Creating web maps with R Shiny

Introduction

The goal for this workshop is to create a basic web mapping application, where you can display polygons and rasters on a basemap. This web map should be accessible to anyone with an internet connection via web browser, like Google Chrome or Mozilla Firefox.

This workshop assumes you have a basic understanding of the following tools: - Git (GitHub) - R programming and using RStudio - Spatial analyses in R using sf and terra packages

The source code and examples for this workshop can be found here: https://github.com/JayMatsushiba/Tutorial-R-Shiny-Web-Mapping

Why web maps?

Before we get into how to build a web mapping application, we should ask ourselves what are the advantages of building these in the first place. With traditional GIS, with software like ArcGIS Pro and QGIS, we can do all sorts of powerful spatial analyses. However, the challenge is sharing our information. While we can send files to other people, the appearance and the interpretation of our data will be different depending on how the recipient accesses the information. This also poses a barrier if we want to engage people that aren’t necessarily GIS experts. With web maps, we can craft visualizations and interactions with our spatial data that are in line with the narrative that we want to communicate. We also make our work much more accessible to a broader audience.

There are web mapping platforms that exist, such as ArcGIS Online, that are capable and can be used to achieve similar results to what we will learn in this workshop. However, these platforms are paid and closed-source, forcing us to be locked in to one company. With creating Web Maps with R Shiny, we stick with open-source software to analyze and visualize the data. We have the flexibility to choose where to host our application depending on our needs, including free options.

Here is an example of what is possible with this combination of tools: https://jaymatsushiba.shinyapps.io/Global_Sharks_Rays_Conservation_Tool/

Other resources that I found helpful for developing this workshop

Happy Git and GitHub for the useR by Jennifer Bryan https://happygitwithr.com/

Shiny Basics https://shiny.posit.co/r/getstarted/shiny-basics/lesson1/index.html

Mastering Shiny by Hadley Wickham https://mastering-shiny.org/index.html

Using Leaflet with Shiny https://rstudio.github.io/leaflet/articles/shiny.html

Part 1: GitHub and R Project Setup

While setting up a project to work with Git and GitHub may be an extra step, it is really useful and important for keeping our work organized and accessible.

Using Git and GitHub in itself can be an entire workshop in itself, so we will keep it as brief and straightforward as possible here. The reason why we use Git and GitHub is for a number of reasons. Firstly, it provides a backup to your work in case something happens to your computer. Secondly, it makes it easier to share code and keep it updated, compared to emailing random files around. Thirdly, it provides many tools for collaboration and is the main reason why I use Git and GitHub as part of my workflow.

You may be confused by the distinction between Git and GitHub. Git refers to the system of organizing code changes, while GitHub is a service that runs Git on the cloud (meaning over the internet on a different computer from your own).

Create new GitHub Repository

A repository in broad terms is a place that stores the code for a given project. We can think of it as a folder with some fancy features, including the ability to keep track of changes made to it over time. The easiest way to create a new GitHub repository is through their website (https://github.com/). Click on the link and follow the instructions to create an account if you don’t have one already. Click on the green button that says “New” to create a new repository Give your repository a name and optionally a description. You can leave the rest of the options as their defaults.

You can name your repository whatever you want, but I suggest something that is descriptive for the project that you are working on. I will be using “Tutorial-R-Shiny-Web-Mapping”

Install Git

Depending on your operating system (Windows, MacOS, Linux), installation instructions for git are different.

See this link for installation recommendations by OS. https://happygitwithr.com/install-git

Create GitHub Personal Access Token

Okay, let’s get started in RStudio! Open the RStudio IDE software.

Running this line of code will open a link in your browser, leading you to create a GitHub Personal Access Token (PAT), which will be used for enabling access to the GitHub repository from RStudio.

usethis::create_github_token()

This is the website that should open upon running the command Keep this window open, because we will need the token displayed at the top of the page for setting our credentials in RStudio.

Setting credentials

We need to add our credentials to RStudio using the next command. Run this in your RStudio console and paste in the PAT from the web page in the previous step.

gitcreds::gitcreds_set()

Create new R Project that is linked to GitHub repository

Now, you should be able to create a new R Project that is linked to the GitHub repository that you created earlier. Click New Project under File in the upper toolbar Select the Version Control option Select the Git option Input the link to your GitHub repository for this project and choose a location to save the project

You should now have a R Project set up that we can work in that is version controlled with GitHub. This means that we can save our progress while backing it up to GitHub, as well as rollback to different versions in case we break something along the way. There is a lot that Git and GitHub can do that can make your work more efficient and easier to share, but that would be outside of the scope of this particular workshop. The Happy Git and GitHub for the useR by Jennifer Bryan (https://happygitwithr.com/) is a really good resource for specifically using GitHub with RStudio, so consult that if you would like to learn more.

Make your first changes through git commit and git push

To show how GitHub works with RStudio, let’s make our first .R file in our project.

Let’s put some content into this spatial.R. Copy and paste the following code into the file. Save the changes to your file.

# This will contain the spatial data analysis code for this workshop.
print("Hello World")

In the upper right window, there are a few tabs, including one that says “Git”. Click that tab to open the view. Click the Git menu in the upper right window

Click the “Commit” button in the “Git” view. This will open a new window. Here, you can decide what changes to your repository to commit, meaning record those changes as a version of your repository.

Check the box for R under “Staged”. This means these selected changes will be part of the next commit. In the “Commit message” box, it is best practice to write a short description about what changes this commit contains. Then, you can click the “Commit” button. After the commit, R should disappear as one of the files listed in the table. To back these changes up onto GitHub, we need to click the “Push” button with the upwards facing green arrow.

Now, if you check back to your GitHub page through your browser, you should be able to see these changes there! That means your Github and RStudio have been set up successfully. You should be repeating this “git commit” and “git push” operation throughout your work as best practice.

Important Note: GitHub has a maximum individual file size of 100 MB, and will not allow uploads for files larger than this. However, git will still allow you to commit additions and changes to large files like this (since this is a GitHub limitation rather than for git). It can be quite a hassle to fix if you accidentally commit a large file, so make sure you do not do this. Simply deleting the large files will not fix the problem, since the large files become part of the repository’s versions after being committed.

Addressing these problems are beyond this workshop, but see here for additional context: https://stackoverflow.com/questions/2100907/how-can-i-remove-delete-a-large-file-from-the-commit-history-in-the-git-reposito

Part 2: Basic Spatial Analysis with sf and terra

Let’s get started on working with spatial data in R! We will continue to work out of R/spatial.R, which should look like this at this point after being committed and pushed to GitHub in the previous step:

# This will contain the spatial data analysis code for this workshop.
print("Hello World")

Let’s remove the print("Hello World") and load the relevant packages. Install the packages if you haven’t done that yet.

# This will contain the spatial data analysis code for this workshop.
library(sf)
library(terra)
library(tidyverse)

We need some spatial data! I have a folder of sample data hosted on GitHub that you can download here: https://github.com/JayMatsushiba/Tutorial-R-Shiny-Web-Mapping.git. You can download the files individually in the data folder, or you can git clone the whole repository (which includes a sample Spatial Shiny App). The process for cloning the repository is actually the same as linking to a new project, so follow the steps in the Create new R Project that is linked to GitHub repository section again, just with the link to my repo instead. Copy the data folder from my repository into your own project.

Common file formats for spatial data include shapefiles (.shp with associated files), .geojson, .geotiff, etc. (there are so many…). Generally though, we can read in vector spatial data with st_read("filepath_here") and raster data with rast("filepath_here").

Let’s try importing some spatial files into R:

# These are iNaturalist observations of the American Black Bear in British Columbia
# This is an example of a point data.
black_bear_observations_bc <- st_read("data/black_bear_observations_bc/black_bear_observations_bc.shp")

# This is a polygon representing the boundaries of Burnaby
# https://data.burnaby.ca/datasets/d903b87782734451ae286cb0b59938ac
burnaby <- st_read("data/Burnaby_Boundary/Burnaby_Boundary.shp")

# This is a multipolygon representing British Columbia
# https://open.canada.ca/data/en/dataset/a883eb14-0c0e-45c4-b8c4-b54c4a819edb 
british_columbia <- st_read("data/british_columbia/british_columbia.shp")

Let’s try doing some basic plots

The spatial information is saved in the geometry column of these objects, so we can access them this way:

plot(black_bear_observations_bc$geometry)

plot(burnaby$geometry)

plot(british_columbia$geometry)

Intersection

Using the st_intersection() function, we can select the points (y) that are within an area (x).

black_bear_observations_burnaby <- st_intersection(x = burnaby, 
                                                   y = black_bear_observations_bc)

We can actually use ggplot to put multiple spatial layers onto one plot. Only the black bear observations within Burnaby are plotted, since we used the st_intersection() to clip the points to the Burnaby boundary.

ggplot() +
  geom_sf(data = burnaby) +
  geom_sf(data = black_bear_observations_burnaby)

Clip

Using the same st_intersection() between two polygon layers will act as a clip, returning the overlapping part of the two polygons.

bc_without_burnaby <- st_intersection(burnaby, british_columbia)

Oh no! An error!

The st_crs() function returns the coordinate system of the spatial object. This error means that the two input spatial objects are not the same coordinate systems and therefore the st_intersection() doesn’t work.

We can check the coordinate systems for each to verify:

st_crs(burnaby) # Coordinate system is WGS 84
st_crs(british_columbia) # Coordinate system is NAD83 / Statistics Canada Lambert

In order to remedy these issues, we can transform one of the layers to match the other.

burnaby_transformed <- st_transform(burnaby, st_crs(british_columbia))
burnaby_bc_clip <- st_intersection(burnaby_transformed, british_columbia)

Now we can look at what this looks like to clip British Columbia to Burnaby. While it looks different due to the transformation, this ends up just being the Burnaby boundary area because Burnaby is entirely contained by British Columbia.

ggplot() +
  geom_sf(data = burnaby_bc_clip)

Buffer

Let’s try making some buffers around Burnaby.

# The second parameter is the distance of the buffer
# The units depend on the coordinate reference system of the input layer. 
burnaby_buffer <- st_buffer(burnaby_transformed, 2000) 

ggplot() +
  geom_sf(data = burnaby_buffer) +
    geom_sf(data = burnaby, fill = "grey") 

Counts

We can count the number of points that fall into each area by using st_intersects(). This returns something different compared to st_intersection(), as it returns the list of indexes of the intersecting rows in the second object.

index <- st_intersects(burnaby, black_bear_observations_bc)

So for a simple count of a single polygon, we could just take the length of this list.

lengths(index)

Let’s do something more interesting with this, and get the counts of observations across British Columbia over a regular grid cell. We need to start with creating the grid cells using st_make_grid().

grid <- st_make_grid(
  british_columbia,
  cellsize = 50000
)

# We can see that we created a regular sized grid over the area of BC. 
ggplot() +
  geom_sf(data = grid) +
  geom_sf(data = british_columbia)

We can do the same st_intersects() operation between this grid and the black bear observations. This will return a list of lists, with one list of row indexes of the black bear observations that intersect with each grid cell.

black_bear_observations_bc_transform <- black_bear_observations_bc %>%
  st_transform(st_crs(grid)) # remembering that the CRS need to be the same 

# This returns a list of lists
index_grid <- st_intersects(grid, black_bear_observations_bc_transform) 

# This turns into a list of counts of observations in each grid cell 
grid_count <- lengths(index_grid)

Since now we have a list of counts, which is ordered based on the grid cell layer, we can simply column bind them together. We can take that matrix and convert to a dataframe first, then into the spatial object (sf). We renamed the grid column to geometry, since that is the default name of the column with the spatial information

# combine grid cells and counts
bear_observation_counts_grid <- cbind(grid, grid_count) %>%
  as.data.frame() %>% # convert to dataframe, to allow conversion to sf
  st_as_sf() %>% # convert to sf
  st_set_crs(st_crs(grid)) %>% # make sure that the original CRS is assigned
  rename(geometry = grid) %>% # rename to default geometry column name
  mutate(grid_count = as.numeric(grid_count))

ggplot() +
  scale_fill_viridis_c() +
  geom_sf(data = bear_observation_counts_grid, 
          aes(fill = grid_count))

And we can clean this up a bit more by intersecting again with British Columbia. This may take quite a long time, so we can skip this. This is also a reason to use st_intersects() instead of st_intersection() when possible.

bear_obs_counts_grids_clip <- st_intersection(bear_observation_counts_grid, british_columbia)

ggplot() +
  scale_fill_viridis_c() +
  geom_sf(data = bear_obs_counts_grids_clip, 
          aes(fill = grid_count))

Part 3: Basic Shiny Apps

To get started, let’s do an introduction on Shiny apps. Shiny apps are a way to create interactive, well, applications using the R language. In order to create Shiny apps, we need to distinguish how they work in contrast to the interactive mode that we usually use to work with R.

In typical R code, we run our code line by line. This is often called scripting, and we know our code runs from the top of the page to the bottom. We can also run this script one line at a time. That means we can write one line of code and run it.

x <- "hello world"

Then we can add another line of code and run it, and this would work correctly.

print(x)

However, when we create Shiny apps and then run them, we don’t have the option of adding new lines of code and building upon the previous steps while the app is running. In other words, the Shiny app will need to contain all of the code to load in files, do the intended operations on them, etc. This will become apparent as we go through the lab. In a Shiny app, the code does not run from top to bottom of the page either, instead relying on something called reactivity for defining which code should run and when.

This part references Mastering Shiny (https://mastering-shiny.org/index.html). Please take a look there for additional context and learning.

Basic Structure of a Shiny App

Let’s create a new file called app.R. This single file will contain all of the code we need for our Shiny app for now. The code below has the basic structure of a Shiny App, and is pretty much the simplest Shiny App that you can create.

# load the shiny library
library(shiny)

# This defines the user interface, which is the part that your users will see and interact with
ui <- fluidPage(
  "Hello, world!"
)

# This defines the behaviour of the app. 
# Currently, this is empty so our application doesn't do anything. 
server <- function(input, output, session) {
}

# This line executes or starts the Shiny app
shinyApp(ui, server)

You should be able to click Run App above the app.R that is open. Screenshot for the Run App button

This should open a new window that shows your application. Screenshot for the Hello World! app

Basic UI

Now this Shiny app is pretty boring, with no interactivity for the user. Let’s add some UI elements to the app. There are a number of premade UI elements that we will use (See https://mastering-shiny.org/basic-ui.html for more details on what is available). We unfortunately won’t have time to go over the range of UI possible with Shiny, as that could be a whole workshop in itself.

Sample Shiny App

Inputs

Let’s look at UI part of app.R again and add a couple lines of code to begin creating the UI. Let’s clear the "Hello World", and create some inputs. We’ll start with a really simple text input and numeric input. These inputs are how we get the user’s input to trigger or change some behaviour of the app.

# This defines the user interface, which is the part that your users will see and interact with
ui <- fluidPage(
  textInput("name", "What is your name?"),
  numericInput("number", "Enter a number", 
               value = 12,
               min = 0, 
               max = 200)
  numericInput(inputId = "number2",
               label = "Enter a number",
               value = 12,
               min = 0,
               max = 200)
)
Basic UI
Basic UI

Let’s also add a button, which we can later use to trigger some actions for the app.

# This defines the user interface, which is the part that your users will see and interact with
ui <- fluidPage(
  textInput(inputId = "name", 
            label = "What is your name?"),
  numericInput(inputId = "number", 
               label = "Enter a number", 
               value = 12,
               min = 0, 
               max = 200),
  numericInput(inputId = "number2",
               label = "Enter a number",
               value = 12,
               min = 0,
               max = 200),
  actionButton(inputId = "click", 
               label = "Click Me!")
)
Basic UI with button
Basic UI with button

Note that every input has an inputId parameter. These have to be unique values across all the UI elements, because this is how we will access the values inputted into those UI elements.

Now, we can access the input values in the server function by the input object. For example, we can access the name inputId object in the server function by using input$name.

Outputs

Outputs are values that are calculated or retrieved by the server in some function, which are then displayed somehow on the UI. Similarly to inputs, outputs will have a unique outputId parameter.

Let’s review the complete app code at this stage. If we run this app, we see some ways to input data but it doesn’t seem to do anything. This is because we are missing outputs and the server code that describes the logic of the app.

library(shiny)
ui <- fluidPage(
  textInput(inputId = "name", 
            label = "What is your name?"),
  numericInput(inputId = "number", 
               label = "Enter a number", 
               value = 12,
               min = 0, 
               max = 200),
  numericInput(inputId = "number2",
               label = "Enter a number",
               value = 12,
               min = 0,
               max = 200),
  actionButton(inputId = "click", 
               label = "Click Me!")
)
server <- function(input, output, session) {
}
shinyApp(ui, server)

Here, we will add a couple more lines to the UI of the app for the outputs. These are two most simple outputs, with textOutput() for regular text and verbatimTextOutput() for code / console outputs.

library(shiny)
ui <- fluidPage(
  textInput(inputId = "name",
            label = "What is your name?"),
  numericInput(inputId = "number",
               label = "Enter a number",
               value = 12,
               min = 0,
               max = 200),
  numericInput(inputId = "number2",
               label = "Enter a number",
               value = 12,
               min = 0,
               max = 200),
  actionButton(inputId = "click",
               label = "Click Me!"),
  textOutput(outputId = "text"),
  verbatimTextOutput(outputId = "code")
)
server <- function(input, output, session) {
}
shinyApp(ui, server)
Basic UI with button
Basic UI with button

Now, if we run this app, still nothing would look different from the previous steps, since we haven’t provided any server logic so far. Let’s add some simple placeholder values into the server function. Similar to inputs, we can access output variables using output$the_outputId_of_output_UI.

server <- function(input, output, session) {
  output$text <- renderText({
    "Hello friend!"
  })
  output$code <- renderPrint({
    5 * 20
  })
}
Basic outputs
Basic outputs

Note that we can’t just pass a string or a bunch of code to each of these outputs. We need to wrap them with a renderText() or renderPrint(). These functions correspond to the the type of output UI they are linking to. Later, we would use renderLeaflet() in order to render maps. This render function is necessary for enabling the output UI elements to update when it’s inputs are changed (reactivity), and for translating R code into HTML (which is the language for the web page).

At this point, we have the basic inputs and outputs necessary to create our app, but our app doesn’t do anything. This is where we need to understand the concept of reactivity as used by Shiny.

Basic Reactivity

Shiny apps use this concept called reactivity, which underpins their behaviour. This can be pretty tough to wrap your head around, since it is really different from scripting. As described earlier, a typical R script will run the code generally in order, from top to bottom. With Shiny apps, the order in which code is run does not follow this structure.

Instead, code is run depending on the changes to the input object, which is defined by the user’s actions. As other variables take the input as a dependency, they will also get updated when input is updated. output is really similar, but in the opposite direction where we change the output object and those changes propagate to the output UI elements.

Let’s try applying reactivity to our app.R. Let’s simplify our code for now, and focus on the textInput and textOutput here. If you try running this Shiny app, you’ll notice that the textOutput updates every time there is a change made to the user input. This means the renderText() is running every time there is a change to input$name.

library(shiny)
ui <- fluidPage(
  textInput(inputId = "name",
            label = "What is your name?"),
  textOutput(outputId = "text")
)
server <- function(input, output, session) {
  output$text <- renderText({
    paste0("Hello ", input$name, "!")
  })
}
shinyApp(ui, server)

Basic reactivity demonstrated We can also move the content of renderText() into a reactive expression using reactive(). This isn’t really necessary here, but it is more useful as your applications become more complex. Something to remember is that reactive() is a function, so we need to write greeting() in order to access the value (i.e., running the function), rather than greeting.

library(shiny)
ui <- fluidPage(
  textInput(inputId = "name",
            label = "What is your name?"),
  textOutput(outputId = "text")
)
server <- function(input, output, session) {
  greeting <- reactive({
    paste0("Hello ", input$name, "!")
  })
  output$text <- renderText({
    greeting()
  })
}
shinyApp(ui, server)

Now, we may not always want reactive behaviour. While the app runs fine updating for every change of input for something small, like one short text string representing a name, it would be a problem for a more complex operation. Imagine you wanted to run a model or spatial analysis that takes a few minutes. You would want to make sure you have all your inputs correct, and then trigger the function. It would be very frustrating if every input change automatically ran the model.

For this purpose, we can use something called eventReactive() along with an actionButton(). This is really similar to what is in the previous chunk, with the main difference that reactive() has been replaced by eventReactive(). The first parameter of eventReactive() is the input action that triggers the change; in our case is the actionButton(). The second parameter is the function that runs when the eventReactive() is triggered.

library(shiny)
ui <- fluidPage(
  textInput(inputId = "name",
            label = "What is your name?"),
  actionButton(inputId = "trigger", label = "Trigger"),
  textOutput(outputId = "text")
)
server <- function(input, output, session) {
  greeting <- eventReactive(input$trigger, {
    paste0("Hello ", input$name, "!")
  })
  output$text <- renderText({
    greeting()
  })
}
shinyApp(ui, server)

There is another function called observeEvent() that I like to use for my Shiny apps, that is very similar to eventReactive(). This is useful if you want to trigger some code without returning its output into a new object. You can learn more about them here: https://mastering-shiny.org/basic-reactivity.html#observers

Challenge!

Now, let’s revisit the application that we had at the end of the UI section. With what you learned about reactivity, can you change the behaviour of this application so that when the button is pressed, the UI outputs update to provide a greeting to the input name and multiplies the two input numbers together?

library(shiny)
ui <- fluidPage(
  textInput(inputId = "name",
            label = "What is your name?"),
  numericInput(inputId = "number",
               label = "Enter a number",
               value = 12,
               min = 0,
               max = 200),
  numericInput(inputId = "number2",
               label = "Enter a number",
               value = 12,
               min = 0,
               max = 200),
  actionButton(inputId = "click",
               label = "Click Me!"),
  textOutput(outputId = "text"),
  verbatimTextOutput(outputId = "code")
)
server <- function(input, output, session) {
  output$text <- renderText({
    "Hello friend!"
  })
  output$code <- renderPrint({
    5 * 20
  })
}
shinyApp(ui, server)

Loading Data into Shiny apps

One last thing we need to learn is how to load in data into the Shiny app. We will demonstrate this with a very simple Shiny app that just prints the code output.

library(shiny)

# Top Level:
# Code at this level runs once when the application is launched 

ui <- fluidPage(
  verbatimTextOutput(outputId = "code")
)
server <- function(input, output, session) {
  # Middle Level: 
  # Code at this level runs once when a user visits the app
  output$code <- renderPrint({
    # Bottom Level: 
    # Code at this level runs every time its contents update
    print("Testing!")
  })
}
shinyApp(ui, server)

Since we only need to load in our data once when the application launches, we can put the line of code at the top level. We can change the print() in output$code to show that the data has been loaded.

library(shiny)
library(sf)

# Top Level:
# Code at this level runs once when the application is launched 
black_bear_obs <- st_read("data/black_bear_observations_bc/black_bear_observations_bc.shp")

ui <- fluidPage(
  verbatimTextOutput(outputId = "code")
)
server <- function(input, output, session) {
  # Middle Level: 
  # Code at this level runs once when a user visits the app
  output$code <- renderPrint({
    # Bottom Level: 
    # Code at this level runs every time its contents update
    print(black_bear_obs)
  })
}
shinyApp(ui, server)

Here is a source that explains how this works: https://shiny.posit.co/r/getstarted/shiny-basics/lesson5/

Part 4: Spatial Data and Shiny Apps

Further reading: https://rstudio.github.io/leaflet/articles/shiny.html

Believe it or not, we already have the foundations necessary to create a simple web map with Shiny! Let’s build off the simple app we created for loading data by adding / swapping a couple lines of code. This continues to follow the pattern of the somethingOutput() on the UI side, paired with the renderSomething() assigned to the output$id.

A new function we are introducing is the leaflet() function. We don’t need to understand how to use it in depth at this point, but it is sort of similar to using something like ggplot() but specifically for web maps.

library(shiny)
library(sf)
library(leaflet)

black_bear_obs <- st_read("data/black_bear_observations_bc/black_bear_observations_bc.shp")

ui <- fluidPage(
  # We can use leafletOutput like the other outputs
  # Just need to pass a value to outputId
  leafletOutput(outputId = "mymap")
)
server <- function(input, output, session) {
  # Creating the output to mymap, which is our leafletOutput
  output$mymap <- renderLeaflet({
    # We create the leaflet object that will be displayed in the app
    leaflet()
  })
}
shinyApp(ui, server)

The most basic Shiny Leaflet web app ### Base Maps This is completely empty map though, which isn’t very useful. Let’s add a base map. A base map is essentially an image that is spatially referenced, and usually used just for providing visual context to the spatial data on the map. We generally cannot use the base map for any additional spatial analyses or access the input data that was used to create them through the base map itself.

library(shiny)
library(sf)
library(leaflet)

black_bear_obs <- st_read("data/black_bear_observations_bc/black_bear_observations_bc.shp")

ui <- fluidPage(
  leafletOutput(outputId = "mymap")
)
server <- function(input, output, session) {
  output$mymap <- renderLeaflet({
    leaflet() %>%
      # This is how we add a basemap on the leaflet
      # provider basically means which base map to use 
      addProviderTiles(provider = providers$CartoDB.Positron)
  })
}
shinyApp(ui, server)
Basic Leaflet app with base map
Basic Leaflet app with base map

This is starting to look more like what we think of as a web map! We can zoom into the map, change our view of the map, etc. At this point though, this doesn’t have any real data that we would like to show. Let’s change that.

Adding Data to Leaflet Map

Let’s add our black bear observations onto this map. This is actually really easy as well, we just need to add a single line of code and use the addCircles() function, feeding the data parameter with the point dataset (black_bear_obs)

library(shiny)
library(sf)
library(leaflet)

black_bear_obs <- st_read("data/black_bear_observations_bc/black_bear_observations_bc.shp")

ui <- fluidPage(
  leafletOutput(outputId = "mymap")
)
server <- function(input, output, session) {
  output$mymap <- renderLeaflet({
    leaflet() %>%
      addProviderTiles(provider = providers$CartoDB.Positron) %>%
      # This is how we add points to a leaflet map
      addCircles(data = black_bear_obs)
  })
}
shinyApp(ui, server)
Basic circle symbols for point data
Basic circle symbols for point data

We can also add popups to each of these circles as well, so that it shows some information from the spatial data when the circle is clicked on. This is relatively simple as well. We just add the popup parameter in addCircles() with the ~column that we want to have appear in the popup.

addCircles(data = black_bear_obs,
           popup = ~observed_1)
Basic popups
Basic popups

This is essentially the same for the other spatial data as well, just with different functions.

# For adding polygons
leaflet() %>%
  addPolygons(data = polygons)

# For adding lines
leaflet() %>%
  addPolylines(data = polylines)

# For adding rasters
leaflet() %>%
  addRasterImage(data = raster_data)

Challenge!

Can you add the Burnaby Boundary polygon to the map?

library(shiny)
library(sf)
library(leaflet)

black_bear_obs <- st_read("data/black_bear_observations_bc/black_bear_observations_bc.shp")
burnaby <- st_read("data/Burnaby_Boundary/Burnaby_Boundary.shp")

ui <- fluidPage(
  leafletOutput(outputId = "mymap")
)
server <- function(input, output, session) {
  output$mymap <- renderLeaflet({
    leaflet() %>%
      addProviderTiles(provider = providers$CartoDB.Positron) %>%
      addCircles(data = black_bear_obs,
                 popup = ~observed_1)
  })
}
shinyApp(ui, server)

Putting everything together!

We are going to make a web map now, with the ability for the user to filter points based off of dates of observations. This will really bring together what we have learnt so far. We will also introduce another library bslib for enhancing the look and feel of our web map.

To start, we’ll introduce this template for the app:

library(shiny)
library(bslib)
library(sf)
library(leaflet)

# Define UI ----
# This is the main difference from what we have been doing
# We use page_sidebar() instead of fluidPage() for the UI
ui <- page_sidebar(
 
)

# Define server logic ----
server <- function(input, output) {

}

# Run the app ----
shinyApp(ui = ui, server = server)

Let’s add our title and sidebar:

library(shiny)
library(bslib)
library(sf)
library(leaflet)

# Define UI ----
# This is the main difference from what we have been doing
# We use page_sidebar() instead of fluidPage() for the UI
ui <- page_sidebar(
  title = "Black Bear Observations Web Map",
  sidebar = sidebar("Filter by Date"),
  "This is where our main content will go"
)

# Define server logic ----
server <- function(input, output) {

}

# Run the app ----
shinyApp(ui = ui, server = server)
Basic sidebar layout
Basic sidebar layout

Let’s add some more UI to our app, including a date range input in the sidebar and a basic Leaflet map as the main content:

library(shiny)
library(bslib)
library(sf)
library(leaflet)

# Define UI ----
ui <- page_sidebar(
  title = "Black Bear Observations Web Map",
  sidebar = sidebar("Filter by Date",
                    dateRangeInput(inputId = "date_range",
                                   label = "Date Range"),
                    actionButton(inputId = "filter_action",
                                 label = "Filter")
  ),
  leafletOutput(outputId = "mymap")
)

# Define server logic ----
server <- function(input, output) {
  output$mymap <- renderLeaflet({
    leaflet() %>%
      addProviderTiles(provider = providers$CartoDB.Positron)
  })
}

# Run the app ----
shinyApp(ui = ui, server = server)
Creating UI with bslib
Creating UI with bslib

Now, let’s add reading in the spatial data and basic visualization of it to the app:

library(shiny)
library(bslib)
library(sf)
library(leaflet)

# Reading in the black bear observations
black_bear_obs <- st_read("data/black_bear_observations_bc/black_bear_observations_bc.shp")

# Define UI ----
ui <- page_sidebar(
  title = "Black Bear Observations Web Map",
  sidebar = sidebar("Filter by Date",
                    dateRangeInput(inputId = "date_range",
                                   label = "Date Range"),
                    actionButton(inputId = "filter_action",
                                 label = "Filter")
  ),
  leafletOutput(outputId = "mymap")
)

# Define server logic ----
server <- function(input, output) {
  output$mymap <- renderLeaflet({
    leaflet() %>%
      addProviderTiles(provider = providers$CartoDB.Positron) %>%
      # Adding the circle visualizations
      addCircles(data = black_bear_obs,
                 popup = ~observed_1)
  })
}

# Run the app ----
shinyApp(ui = ui, server = server)
Adding the bear points to the web application
Adding the bear points to the web application

Let’s set up accessing the date range inputs, and show the selected dates with a simple text output in the side bar.

library(shiny)
library(bslib)
library(sf)
library(leaflet)

black_bear_obs <- st_read("data/black_bear_observations_bc/black_bear_observations_bc.shp")

# Define UI ----
ui <- page_sidebar(
  title = "Black Bear Observations Web Map",
  sidebar = sidebar("Filter by Date",
                    dateRangeInput(inputId = "date_range",
                                   label = "Date Range"),
                    actionButton(inputId = "filter_action",
                                 label = "Filter"),
                    # Adding some simple text outputs
                    textOutput(outputId = "text_start_date"),
                    textOutput(outputId = "text_end_date")
  ),
  leafletOutput(outputId = "mymap")
)

# Define server logic ----
server <- function(input, output) {
  # The eventReactive makes this update on the button press
  start_date <- eventReactive(input$filter_action, {
    # dateRangeInput() provides a vector of length 2
    # the first object in this vector is the first date
    input$date_range[1]
  })
  end_date <- eventReactive(input$filter_action, {
    # the second object in this vector is the second date
    input$date_range[2]
  })
  # These will automatically update when the reactive values update
  # start_date() and end_date() are reactive values
  output$text_start_date <- renderText({
    paste0("Start date: ", start_date())
  })
  output$text_end_date <- renderText({
    paste0("End date: ",  end_date())
  })

  output$mymap <- renderLeaflet({
    leaflet() %>%
      addProviderTiles(provider = providers$CartoDB.Positron) %>%
      addCircles(data = black_bear_obs,
                 popup = ~observed_1)
  })
}

# Run the app ----
shinyApp(ui = ui, server = server)
Button press updates the start and end dates
Button press updates the start and end dates

Time to make the big jump, and add some basic data processing within the application. We will be querying the black_bear_obs points by the dates that fall between the start_date and end_date.

library(shiny)
library(bslib)
library(sf)
library(leaflet)

black_bear_obs <- st_read("data/black_bear_observations_bc/black_bear_observations_bc.shp")

# Define UI ----
ui <- page_sidebar(
  title = "Black Bear Observations Web Map",
  sidebar = sidebar("Filter by Date",
                    dateRangeInput(inputId = "date_range",
                                   label = "Date Range"),
                    actionButton(inputId = "filter_action",
                                 label = "Filter"),
                    # Adding some simple text outputs
                    textOutput(outputId = "text_start_date"),
                    textOutput(outputId = "text_end_date")
  ),
  leafletOutput(outputId = "mymap")
)

# Define server logic ----
server <- function(input, output) {
  start_date <- eventReactive(input$filter_action, {
    input$date_range[1]
  })
  end_date <- eventReactive(input$filter_action, {
    input$date_range[2]
  })
  output$text_start_date <- renderText({
    paste0("Start date: ", start_date())
  })
  output$text_end_date <- renderText({
    paste0("End date: ",  end_date())
  })

  # Filtering the values of the black_bear_obs
  # This needs to be in a reactive(), since this will trigger when its inputs are changed
  # The inputs are changed with the eventReactive(), controlled by the button
  # Therefore, this selected_black_bear_obs will also update each time the button is pressed
  selected_black_bear_obs <- reactive({
    # This queries dates that are within the start and end dates
    black_bear_obs[black_bear_obs$observed_1 >= start_date() & black_bear_obs$observed_1 <= end_date(),]
  })

  output$mymap <- renderLeaflet({
    leaflet() %>%
      addProviderTiles(provider = providers$CartoDB.Positron) %>%
      # Remember to switch the data to the selected_black_bear_obs()
      addCircles(data = selected_black_bear_obs(),
                 popup = ~observed_1)
  })
}

# Run the app ----
shinyApp(ui = ui, server = server)
Final web map output!!
Final web map output!!

Congratulations, you have created your first fully fledged R Shiny web map! This is a lot to take in, so please pat yourself on the back for getting this far.

Challenge!

Can you create an interactive web map that displays the grid cell output with observation counts from Part 2?

Part 5: Publishing Your Shiny Apps

Now that you have created your web map, we want to share it!

We will be using www.shinyapps.io to share our application to anyone in the world with an internet connection and a browser.

Full Getting Started Guide: https://docs.posit.co/shinyapps.io/guide/getting_started/

Step 1: Create an account

Go to www.shinyapps.io and follow the instructions for creating an account. They provide the option for joining with GitHub, so you should be able to one-click create a new account.

Step 2: Set up

Install the rsconnect package, which we need to connect to shinyapps.io

install.packages("rsconnect")
library(rsconnect)

Step 3: Get your token

Click your account name in the upper right part of the page, then click the Tokens option. Tokens menu option

This should open the Tokens page. Click on Show on one of the tokens in the table, and this should open a pop up with instructions on how to authorize in Rstudio to enable deploying your application. Follow these instructions Authorization instructions

Step 4: Publish your app!

Cick on the Publish button in the upper right corner of your Shiny app code(probably called app.R). Publish button

You should see in the popup menu that your account is connected. Give your application a name and click that publish button. This may take a little while to deploy, so take this opportunity to stand up and stretch, move around, relax. Publishing your app to the world!

Step 5: Share your app with your friends and colleagues!

You can go onto www.shinyapps.io and log in to your account. You should be able to see your web map in the Applications page. Click on it, and it should provide the details for your web map. There should be a link to access your app here! Shiny app details on shinyapps.io

If everything has gone correctly, you should be done! Congratulations!

Here is my example Shiny app for this workshop: https://jaymatsushiba.shinyapps.io/Tutorial-R-Shiny-Web-Mapping/

Thank you for following along! If you need any help, feel free to reach out at

This site was originally developed for Web Mapping with R Shiny Workshop Series at SFU delivered on September 20 and September 27, 2024.

LS0tCnRpdGxlOiAiUiBTaGlueSBXZWIgTWFwIFR1dG9yaWFsIgphdXRob3I6ICJKYXkgTWF0c3VzaGliYSIKZGF0ZTogIjIwMjQtMDktMTkiCm91dHB1dDogCiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICB0aGVtZTogdW5pdGVkCiAgaHRtbF9ub3RlYm9vazogCiAgICB0b2M6IHRydWUKZWRpdG9yX29wdGlvbnM6IAogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpgYGAKCiMgQ3JlYXRpbmcgd2ViIG1hcHMgd2l0aCBSIFNoaW55CgoKCiMjIEludHJvZHVjdGlvbgpUaGUgZ29hbCBmb3IgdGhpcyB3b3Jrc2hvcCBpcyB0byBjcmVhdGUgYSBiYXNpYyB3ZWIgbWFwcGluZyBhcHBsaWNhdGlvbiwgd2hlcmUgeW91IGNhbiBkaXNwbGF5IHBvbHlnb25zIGFuZCByYXN0ZXJzIG9uIGEgYmFzZW1hcC4gVGhpcyB3ZWIgbWFwIHNob3VsZCBiZSBhY2Nlc3NpYmxlIHRvIGFueW9uZSB3aXRoIGFuIGludGVybmV0IGNvbm5lY3Rpb24gdmlhIHdlYiBicm93c2VyLCBsaWtlIEdvb2dsZSBDaHJvbWUgb3IgTW96aWxsYSBGaXJlZm94LiAKClRoaXMgd29ya3Nob3AgYXNzdW1lcyB5b3UgaGF2ZSBhIGJhc2ljIHVuZGVyc3RhbmRpbmcgb2YgdGhlIGZvbGxvd2luZyB0b29sczogCi0gR2l0IChHaXRIdWIpCi0gUiBwcm9ncmFtbWluZyBhbmQgdXNpbmcgUlN0dWRpbyAKLSBTcGF0aWFsIGFuYWx5c2VzIGluIFIgdXNpbmcgYHNmYCBhbmQgYHRlcnJhYCBwYWNrYWdlcyAKClRoZSBzb3VyY2UgY29kZSBhbmQgZXhhbXBsZXMgZm9yIHRoaXMgd29ya3Nob3AgY2FuIGJlIGZvdW5kIGhlcmU6IGh0dHBzOi8vZ2l0aHViLmNvbS9KYXlNYXRzdXNoaWJhL1R1dG9yaWFsLVItU2hpbnktV2ViLU1hcHBpbmcgCgojIyMgV2h5IHdlYiBtYXBzPyAKQmVmb3JlIHdlIGdldCBpbnRvIGhvdyB0byBidWlsZCBhIHdlYiBtYXBwaW5nIGFwcGxpY2F0aW9uLCB3ZSBzaG91bGQgYXNrIG91cnNlbHZlcyB3aGF0IGFyZSB0aGUgYWR2YW50YWdlcyBvZiBidWlsZGluZyB0aGVzZSBpbiB0aGUgZmlyc3QgcGxhY2UuIFdpdGggdHJhZGl0aW9uYWwgR0lTLCB3aXRoIHNvZnR3YXJlIGxpa2UgQXJjR0lTIFBybyBhbmQgUUdJUywgd2UgY2FuIGRvIGFsbCBzb3J0cyBvZiBwb3dlcmZ1bCBzcGF0aWFsIGFuYWx5c2VzLiBIb3dldmVyLCB0aGUgY2hhbGxlbmdlIGlzIHNoYXJpbmcgb3VyIGluZm9ybWF0aW9uLiBXaGlsZSB3ZSBjYW4gc2VuZCBmaWxlcyB0byBvdGhlciBwZW9wbGUsIHRoZSBhcHBlYXJhbmNlIGFuZCB0aGUgaW50ZXJwcmV0YXRpb24gb2Ygb3VyIGRhdGEgd2lsbCBiZSBkaWZmZXJlbnQgZGVwZW5kaW5nIG9uIGhvdyB0aGUgcmVjaXBpZW50IGFjY2Vzc2VzIHRoZSBpbmZvcm1hdGlvbi4gVGhpcyBhbHNvIHBvc2VzIGEgYmFycmllciBpZiB3ZSB3YW50IHRvIGVuZ2FnZSBwZW9wbGUgdGhhdCBhcmVuJ3QgbmVjZXNzYXJpbHkgR0lTIGV4cGVydHMuIFdpdGggd2ViIG1hcHMsIHdlIGNhbiBjcmFmdCB2aXN1YWxpemF0aW9ucyBhbmQgaW50ZXJhY3Rpb25zIHdpdGggb3VyIHNwYXRpYWwgZGF0YSB0aGF0IGFyZSBpbiBsaW5lIHdpdGggdGhlIG5hcnJhdGl2ZSB0aGF0IHdlIHdhbnQgdG8gY29tbXVuaWNhdGUuIFdlIGFsc28gbWFrZSBvdXIgd29yayBtdWNoIG1vcmUgYWNjZXNzaWJsZSB0byBhIGJyb2FkZXIgYXVkaWVuY2UuIAoKVGhlcmUgYXJlIHdlYiBtYXBwaW5nIHBsYXRmb3JtcyB0aGF0IGV4aXN0LCBzdWNoIGFzIEFyY0dJUyBPbmxpbmUsIHRoYXQgYXJlIGNhcGFibGUgYW5kIGNhbiBiZSB1c2VkIHRvIGFjaGlldmUgc2ltaWxhciByZXN1bHRzIHRvIHdoYXQgd2Ugd2lsbCBsZWFybiBpbiB0aGlzIHdvcmtzaG9wLiBIb3dldmVyLCB0aGVzZSBwbGF0Zm9ybXMgYXJlIHBhaWQgYW5kIGNsb3NlZC1zb3VyY2UsIGZvcmNpbmcgdXMgdG8gYmUgbG9ja2VkIGluIHRvIG9uZSBjb21wYW55LiBXaXRoIGNyZWF0aW5nIFdlYiBNYXBzIHdpdGggUiBTaGlueSwgd2Ugc3RpY2sgd2l0aCBvcGVuLXNvdXJjZSBzb2Z0d2FyZSB0byBhbmFseXplIGFuZCB2aXN1YWxpemUgdGhlIGRhdGEuIFdlIGhhdmUgdGhlIGZsZXhpYmlsaXR5IHRvIGNob29zZSB3aGVyZSB0byBob3N0IG91ciBhcHBsaWNhdGlvbiBkZXBlbmRpbmcgb24gb3VyIG5lZWRzLCBpbmNsdWRpbmcgZnJlZSBvcHRpb25zLiAKCkhlcmUgaXMgYW4gZXhhbXBsZSBvZiB3aGF0IGlzIHBvc3NpYmxlIHdpdGggdGhpcyBjb21iaW5hdGlvbiBvZiB0b29sczoKaHR0cHM6Ly9qYXltYXRzdXNoaWJhLnNoaW55YXBwcy5pby9HbG9iYWxfU2hhcmtzX1JheXNfQ29uc2VydmF0aW9uX1Rvb2wvIAoKIyMjIE90aGVyIHJlc291cmNlcyB0aGF0IEkgZm91bmQgaGVscGZ1bCBmb3IgZGV2ZWxvcGluZyB0aGlzIHdvcmtzaG9wCkhhcHB5IEdpdCBhbmQgR2l0SHViIGZvciB0aGUgdXNlUiBieSBKZW5uaWZlciBCcnlhbiAKaHR0cHM6Ly9oYXBweWdpdHdpdGhyLmNvbS8KClNoaW55IEJhc2ljcyAKaHR0cHM6Ly9zaGlueS5wb3NpdC5jby9yL2dldHN0YXJ0ZWQvc2hpbnktYmFzaWNzL2xlc3NvbjEvaW5kZXguaHRtbAoKTWFzdGVyaW5nIFNoaW55IGJ5IEhhZGxleSBXaWNraGFtCmh0dHBzOi8vbWFzdGVyaW5nLXNoaW55Lm9yZy9pbmRleC5odG1sIAoKVXNpbmcgTGVhZmxldCB3aXRoIFNoaW55IApodHRwczovL3JzdHVkaW8uZ2l0aHViLmlvL2xlYWZsZXQvYXJ0aWNsZXMvc2hpbnkuaHRtbAoKIyMgUGFydCAxOiBHaXRIdWIgYW5kIFIgUHJvamVjdCBTZXR1cCAKV2hpbGUgc2V0dGluZyB1cCBhIHByb2plY3QgdG8gd29yayB3aXRoIEdpdCBhbmQgR2l0SHViIG1heSBiZSBhbiBleHRyYSBzdGVwLCBpdCBpcyByZWFsbHkgdXNlZnVsIGFuZCBpbXBvcnRhbnQgZm9yIGtlZXBpbmcgb3VyIHdvcmsgb3JnYW5pemVkIGFuZCBhY2Nlc3NpYmxlLiAKClVzaW5nIEdpdCBhbmQgR2l0SHViIGluIGl0c2VsZiBjYW4gYmUgYW4gZW50aXJlIHdvcmtzaG9wIGluIGl0c2VsZiwgc28gd2Ugd2lsbCBrZWVwIGl0IGFzIGJyaWVmIGFuZCBzdHJhaWdodGZvcndhcmQgYXMgcG9zc2libGUgaGVyZS4gVGhlIHJlYXNvbiB3aHkgd2UgdXNlIEdpdCBhbmQgR2l0SHViIGlzIGZvciBhIG51bWJlciBvZiByZWFzb25zLiBGaXJzdGx5LCBpdCBwcm92aWRlcyBhIGJhY2t1cCB0byB5b3VyIHdvcmsgaW4gY2FzZSBzb21ldGhpbmcgaGFwcGVucyB0byB5b3VyIGNvbXB1dGVyLiBTZWNvbmRseSwgaXQgbWFrZXMgaXQgZWFzaWVyIHRvIHNoYXJlIGNvZGUgYW5kIGtlZXAgaXQgdXBkYXRlZCwgY29tcGFyZWQgdG8gZW1haWxpbmcgcmFuZG9tIGZpbGVzIGFyb3VuZC4gVGhpcmRseSwgaXQgcHJvdmlkZXMgbWFueSB0b29scyBmb3IgY29sbGFib3JhdGlvbiBhbmQgaXMgdGhlIG1haW4gcmVhc29uIHdoeSBJIHVzZSBHaXQgYW5kIEdpdEh1YiBhcyBwYXJ0IG9mIG15IHdvcmtmbG93LiAKCllvdSBtYXkgYmUgY29uZnVzZWQgYnkgdGhlIGRpc3RpbmN0aW9uIGJldHdlZW4gR2l0IGFuZCBHaXRIdWIuIEdpdCByZWZlcnMgdG8gdGhlIHN5c3RlbSBvZiBvcmdhbml6aW5nIGNvZGUgY2hhbmdlcywgd2hpbGUgR2l0SHViIGlzIGEgc2VydmljZSB0aGF0IHJ1bnMgR2l0IG9uIHRoZSBjbG91ZCAobWVhbmluZyBvdmVyIHRoZSBpbnRlcm5ldCBvbiBhIGRpZmZlcmVudCBjb21wdXRlciBmcm9tIHlvdXIgb3duKS4gCgojIyMgQ3JlYXRlIG5ldyBHaXRIdWIgUmVwb3NpdG9yeQpBIHJlcG9zaXRvcnkgaW4gYnJvYWQgdGVybXMgaXMgYSBwbGFjZSB0aGF0IHN0b3JlcyB0aGUgY29kZSBmb3IgYSBnaXZlbiBwcm9qZWN0LiBXZSBjYW4gdGhpbmsgb2YgaXQgYXMgYSBmb2xkZXIgd2l0aCBzb21lIGZhbmN5IGZlYXR1cmVzLCBpbmNsdWRpbmcgdGhlIGFiaWxpdHkgdG8ga2VlcCB0cmFjayBvZiBjaGFuZ2VzIG1hZGUgdG8gaXQgb3ZlciB0aW1lLiBUaGUgZWFzaWVzdCB3YXkgdG8gY3JlYXRlIGEgbmV3IEdpdEh1YiByZXBvc2l0b3J5IGlzIHRocm91Z2ggdGhlaXIgd2Vic2l0ZSAoaHR0cHM6Ly9naXRodWIuY29tLykuIENsaWNrIG9uIHRoZSBsaW5rIGFuZCBmb2xsb3cgdGhlIGluc3RydWN0aW9ucyB0byBjcmVhdGUgYW4gYWNjb3VudCBpZiB5b3UgZG9uJ3QgaGF2ZSBvbmUgYWxyZWFkeS4gCiFbQ2xpY2sgb24gdGhlIGdyZWVuIGJ1dHRvbiB0aGF0IHNheXMgIk5ldyIgdG8gY3JlYXRlIGEgbmV3IHJlcG9zaXRvcnldKGltYWdlcy9naXRodWJfbmV3cmVwby5wbmcpCiFbR2l2ZSB5b3VyIHJlcG9zaXRvcnkgYSBuYW1lIGFuZCBvcHRpb25hbGx5IGEgZGVzY3JpcHRpb24uIFlvdSBjYW4gbGVhdmUgdGhlIHJlc3Qgb2YgdGhlIG9wdGlvbnMgYXMgdGhlaXIgZGVmYXVsdHMuXShpbWFnZXMvZ2l0aHViX25ld3JlcG9fZGV0YWlscy5wbmcpCgpZb3UgY2FuIG5hbWUgeW91ciByZXBvc2l0b3J5IHdoYXRldmVyIHlvdSB3YW50LCBidXQgSSBzdWdnZXN0IHNvbWV0aGluZyB0aGF0IGlzIGRlc2NyaXB0aXZlIGZvciB0aGUgcHJvamVjdCB0aGF0IHlvdSBhcmUgd29ya2luZyBvbi4gSSB3aWxsIGJlIHVzaW5nICJUdXRvcmlhbC1SLVNoaW55LVdlYi1NYXBwaW5nIgoKIyMjIEluc3RhbGwgR2l0CkRlcGVuZGluZyBvbiB5b3VyIG9wZXJhdGluZyBzeXN0ZW0gKFdpbmRvd3MsIE1hY09TLCBMaW51eCksIGluc3RhbGxhdGlvbiBpbnN0cnVjdGlvbnMgZm9yIGdpdCBhcmUgZGlmZmVyZW50LiAKClNlZSB0aGlzIGxpbmsgZm9yIGluc3RhbGxhdGlvbiByZWNvbW1lbmRhdGlvbnMgYnkgT1MuIApodHRwczovL2hhcHB5Z2l0d2l0aHIuY29tL2luc3RhbGwtZ2l0IAoKIyMjIENyZWF0ZSBHaXRIdWIgUGVyc29uYWwgQWNjZXNzIFRva2VuCk9rYXksIGxldCdzIGdldCBzdGFydGVkIGluIFJTdHVkaW8hIE9wZW4gdGhlIFJTdHVkaW8gSURFIHNvZnR3YXJlLiAKClJ1bm5pbmcgdGhpcyBsaW5lIG9mIGNvZGUgd2lsbCBvcGVuIGEgbGluayBpbiB5b3VyIGJyb3dzZXIsIGxlYWRpbmcgeW91IHRvIGNyZWF0ZSBhIEdpdEh1YiBQZXJzb25hbCBBY2Nlc3MgVG9rZW4gKFBBVCksIHdoaWNoIHdpbGwgYmUgdXNlZCBmb3IgZW5hYmxpbmcgYWNjZXNzIHRvIHRoZSBHaXRIdWIgcmVwb3NpdG9yeSBmcm9tIFJTdHVkaW8uIApgYGB7ciBjcmVhdGVfZ2l0aHViX3Rva2VuLCBldmFsPUZBTFNFfQp1c2V0aGlzOjpjcmVhdGVfZ2l0aHViX3Rva2VuKCkKYGBgCiFbVGhpcyBpcyB0aGUgd2Vic2l0ZSB0aGF0IHNob3VsZCBvcGVuIHVwb24gcnVubmluZyB0aGUgY29tbWFuZF0oaW1hZ2VzL2dpdGh1Yl9wYXQucG5nKQpLZWVwIHRoaXMgd2luZG93IG9wZW4sIGJlY2F1c2Ugd2Ugd2lsbCBuZWVkIHRoZSB0b2tlbiBkaXNwbGF5ZWQgYXQgdGhlIHRvcCBvZiB0aGUgcGFnZSBmb3Igc2V0dGluZyBvdXIgY3JlZGVudGlhbHMgaW4gUlN0dWRpby4gCgojIyMgU2V0dGluZyBjcmVkZW50aWFscyAKV2UgbmVlZCB0byBhZGQgb3VyIGNyZWRlbnRpYWxzIHRvIFJTdHVkaW8gdXNpbmcgdGhlIG5leHQgY29tbWFuZC4gUnVuIHRoaXMgaW4geW91ciBSU3R1ZGlvIGNvbnNvbGUgYW5kIHBhc3RlIGluIHRoZSBQQVQgZnJvbSB0aGUgd2ViIHBhZ2UgaW4gdGhlIHByZXZpb3VzIHN0ZXAuIApgYGB7ciBnaXRjcmVkc19zZXQsIGV2YWw9RkFMU0V9CmdpdGNyZWRzOjpnaXRjcmVkc19zZXQoKQpgYGAKCiMjIyBDcmVhdGUgbmV3IFIgUHJvamVjdCB0aGF0IGlzIGxpbmtlZCB0byBHaXRIdWIgcmVwb3NpdG9yeSAKTm93LCB5b3Ugc2hvdWxkIGJlIGFibGUgdG8gY3JlYXRlIGEgbmV3IFIgUHJvamVjdCB0aGF0IGlzIGxpbmtlZCB0byB0aGUgR2l0SHViIHJlcG9zaXRvcnkgdGhhdCB5b3UgY3JlYXRlZCBlYXJsaWVyLiAKIVtDbGljayBOZXcgUHJvamVjdCB1bmRlciBGaWxlIGluIHRoZSB1cHBlciB0b29sYmFyXShpbWFnZXMvbmV3X3Byb2plY3QucG5nKQohW1NlbGVjdCB0aGUgVmVyc2lvbiBDb250cm9sIG9wdGlvbl0oaW1hZ2VzL25ld19wcm9qZWN0X3ZlcnNpb25fY29udHJvbC5wbmcpCiFbU2VsZWN0IHRoZSBHaXQgb3B0aW9uXShpbWFnZXMvbmV3X3Byb2plY3RfZ2l0LnBuZykKIVtJbnB1dCB0aGUgbGluayB0byB5b3VyIEdpdEh1YiByZXBvc2l0b3J5IGZvciB0aGlzIHByb2plY3QgYW5kIGNob29zZSBhIGxvY2F0aW9uIHRvIHNhdmUgdGhlIHByb2plY3RdKGltYWdlcy9uZXdfcHJvamVjdF9jbG9uZV9naXRfcmVwby5wbmcpCgpZb3Ugc2hvdWxkIG5vdyBoYXZlIGEgUiBQcm9qZWN0IHNldCB1cCB0aGF0IHdlIGNhbiB3b3JrIGluIHRoYXQgaXMgdmVyc2lvbiBjb250cm9sbGVkIHdpdGggR2l0SHViLiBUaGlzIG1lYW5zIHRoYXQgd2UgY2FuIHNhdmUgb3VyIHByb2dyZXNzIHdoaWxlIGJhY2tpbmcgaXQgdXAgdG8gR2l0SHViLCBhcyB3ZWxsIGFzIHJvbGxiYWNrIHRvIGRpZmZlcmVudCB2ZXJzaW9ucyBpbiBjYXNlIHdlIGJyZWFrIHNvbWV0aGluZyBhbG9uZyB0aGUgd2F5LiBUaGVyZSBpcyBhIGxvdCB0aGF0IEdpdCBhbmQgR2l0SHViIGNhbiBkbyB0aGF0IGNhbiBtYWtlIHlvdXIgd29yayBtb3JlIGVmZmljaWVudCBhbmQgZWFzaWVyIHRvIHNoYXJlLCBidXQgdGhhdCB3b3VsZCBiZSBvdXRzaWRlIG9mIHRoZSBzY29wZSBvZiB0aGlzIHBhcnRpY3VsYXIgd29ya3Nob3AuIFRoZSAqKkhhcHB5IEdpdCBhbmQgR2l0SHViIGZvciB0aGUgdXNlUioqIGJ5IEplbm5pZmVyIEJyeWFuIChodHRwczovL2hhcHB5Z2l0d2l0aHIuY29tLykgaXMgYSByZWFsbHkgZ29vZCByZXNvdXJjZSBmb3Igc3BlY2lmaWNhbGx5IHVzaW5nIEdpdEh1YiB3aXRoIFJTdHVkaW8sIHNvIGNvbnN1bHQgdGhhdCBpZiB5b3Ugd291bGQgbGlrZSB0byBsZWFybiBtb3JlLiAKCiMjIyBNYWtlIHlvdXIgZmlyc3QgY2hhbmdlcyB0aHJvdWdoIGBnaXQgY29tbWl0YCBhbmQgYGdpdCBwdXNoYApUbyBzaG93IGhvdyBHaXRIdWIgd29ya3Mgd2l0aCBSU3R1ZGlvLCBsZXQncyBtYWtlIG91ciBmaXJzdCAuUiBmaWxlIGluIG91ciBwcm9qZWN0LiAKCkxldCdzIHB1dCBzb21lIGNvbnRlbnQgaW50byB0aGlzIGBzcGF0aWFsLlJgLiBDb3B5IGFuZCBwYXN0ZSB0aGUgZm9sbG93aW5nIGNvZGUgaW50byB0aGUgZmlsZS4gU2F2ZSB0aGUgY2hhbmdlcyB0byB5b3VyIGZpbGUuIApgYGB7cn0KIyBUaGlzIHdpbGwgY29udGFpbiB0aGUgc3BhdGlhbCBkYXRhIGFuYWx5c2lzIGNvZGUgZm9yIHRoaXMgd29ya3Nob3AuCnByaW50KCJIZWxsbyBXb3JsZCIpCmBgYAoKSW4gdGhlIHVwcGVyIHJpZ2h0IHdpbmRvdywgdGhlcmUgYXJlIGEgZmV3IHRhYnMsIGluY2x1ZGluZyBvbmUgdGhhdCBzYXlzICJHaXQiLiBDbGljayB0aGF0IHRhYiB0byBvcGVuIHRoZSB2aWV3LiAKIVtDbGljayB0aGUgR2l0IG1lbnUgaW4gdGhlIHVwcGVyIHJpZ2h0IHdpbmRvd10oaW1hZ2VzL3JzdHVkaW9fZ2l0bWVudS5wbmcpCgpDbGljayB0aGUgIkNvbW1pdCIgYnV0dG9uIGluIHRoZSAiR2l0IiB2aWV3LiBUaGlzIHdpbGwgb3BlbiBhIG5ldyB3aW5kb3cuIEhlcmUsIHlvdSBjYW4gZGVjaWRlIHdoYXQgY2hhbmdlcyB0byB5b3VyIHJlcG9zaXRvcnkgdG8gY29tbWl0LCBtZWFuaW5nIHJlY29yZCB0aG9zZSBjaGFuZ2VzIGFzIGEgdmVyc2lvbiBvZiB5b3VyIHJlcG9zaXRvcnkuIAoKQ2hlY2sgdGhlIGJveCBmb3IgYFJgIHVuZGVyICJTdGFnZWQiLiBUaGlzIG1lYW5zIHRoZXNlIHNlbGVjdGVkIGNoYW5nZXMgd2lsbCBiZSBwYXJ0IG9mIHRoZSBuZXh0IGNvbW1pdC4gSW4gdGhlICJDb21taXQgbWVzc2FnZSIgYm94LCBpdCBpcyBiZXN0IHByYWN0aWNlIHRvIHdyaXRlIGEgc2hvcnQgZGVzY3JpcHRpb24gYWJvdXQgd2hhdCBjaGFuZ2VzIHRoaXMgY29tbWl0IGNvbnRhaW5zLiBUaGVuLCB5b3UgY2FuIGNsaWNrIHRoZSAiQ29tbWl0IiBidXR0b24uIEFmdGVyIHRoZSBjb21taXQsIGBSYCBzaG91bGQgZGlzYXBwZWFyIGFzIG9uZSBvZiB0aGUgZmlsZXMgbGlzdGVkIGluIHRoZSB0YWJsZS4gVG8gYmFjayB0aGVzZSBjaGFuZ2VzIHVwIG9udG8gR2l0SHViLCB3ZSBuZWVkIHRvIGNsaWNrIHRoZSAiUHVzaCIgYnV0dG9uIHdpdGggdGhlIHVwd2FyZHMgZmFjaW5nIGdyZWVuIGFycm93LiAKCk5vdywgaWYgeW91IGNoZWNrIGJhY2sgdG8geW91ciBHaXRIdWIgcGFnZSB0aHJvdWdoIHlvdXIgYnJvd3NlciwgeW91IHNob3VsZCBiZSBhYmxlIHRvIHNlZSB0aGVzZSBjaGFuZ2VzIHRoZXJlISBUaGF0IG1lYW5zIHlvdXIgR2l0aHViIGFuZCBSU3R1ZGlvIGhhdmUgYmVlbiBzZXQgdXAgc3VjY2Vzc2Z1bGx5LiBZb3Ugc2hvdWxkIGJlIHJlcGVhdGluZyB0aGlzICJnaXQgY29tbWl0IiBhbmQgImdpdCBwdXNoIiBvcGVyYXRpb24gdGhyb3VnaG91dCB5b3VyIHdvcmsgYXMgYmVzdCBwcmFjdGljZS4gCgoqKkltcG9ydGFudCBOb3RlOioqCkdpdEh1YiBoYXMgYSBtYXhpbXVtIGluZGl2aWR1YWwgZmlsZSBzaXplIG9mIDEwMCBNQiwgYW5kIHdpbGwgbm90IGFsbG93IHVwbG9hZHMgZm9yIGZpbGVzIGxhcmdlciB0aGFuIHRoaXMuIEhvd2V2ZXIsIGdpdCB3aWxsIHN0aWxsIGFsbG93IHlvdSB0byBjb21taXQgYWRkaXRpb25zIGFuZCBjaGFuZ2VzIHRvIGxhcmdlIGZpbGVzIGxpa2UgdGhpcyAoc2luY2UgdGhpcyBpcyBhIEdpdEh1YiBsaW1pdGF0aW9uIHJhdGhlciB0aGFuIGZvciBnaXQpLiBJdCBjYW4gYmUgcXVpdGUgYSBoYXNzbGUgdG8gZml4IGlmIHlvdSBhY2NpZGVudGFsbHkgY29tbWl0IGEgbGFyZ2UgZmlsZSwgc28gbWFrZSBzdXJlIHlvdSBkbyBub3QgZG8gdGhpcy4gU2ltcGx5IGRlbGV0aW5nIHRoZSBsYXJnZSBmaWxlcyB3aWxsIG5vdCBmaXggdGhlIHByb2JsZW0sIHNpbmNlIHRoZSBsYXJnZSBmaWxlcyBiZWNvbWUgcGFydCBvZiB0aGUgcmVwb3NpdG9yeSdzIHZlcnNpb25zIGFmdGVyIGJlaW5nIGNvbW1pdHRlZC4gCgpBZGRyZXNzaW5nIHRoZXNlIHByb2JsZW1zIGFyZSBiZXlvbmQgdGhpcyB3b3Jrc2hvcCwgYnV0IHNlZSBoZXJlIGZvciBhZGRpdGlvbmFsIGNvbnRleHQ6IApodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy8yMTAwOTA3L2hvdy1jYW4taS1yZW1vdmUtZGVsZXRlLWEtbGFyZ2UtZmlsZS1mcm9tLXRoZS1jb21taXQtaGlzdG9yeS1pbi10aGUtZ2l0LXJlcG9zaXRvIAoKIyMgUGFydCAyOiBCYXNpYyBTcGF0aWFsIEFuYWx5c2lzIHdpdGggYHNmYCBhbmQgYHRlcnJhYApMZXQncyBnZXQgc3RhcnRlZCBvbiB3b3JraW5nIHdpdGggc3BhdGlhbCBkYXRhIGluIFIhIFdlIHdpbGwgY29udGludWUgdG8gd29yayBvdXQgb2YgYFIvc3BhdGlhbC5SYCwgd2hpY2ggc2hvdWxkIGxvb2sgbGlrZSB0aGlzIGF0IHRoaXMgcG9pbnQgYWZ0ZXIgYmVpbmcgY29tbWl0dGVkIGFuZCBwdXNoZWQgdG8gR2l0SHViIGluIHRoZSBwcmV2aW91cyBzdGVwOgpgYGB7cn0KIyBUaGlzIHdpbGwgY29udGFpbiB0aGUgc3BhdGlhbCBkYXRhIGFuYWx5c2lzIGNvZGUgZm9yIHRoaXMgd29ya3Nob3AuCnByaW50KCJIZWxsbyBXb3JsZCIpCmBgYAoKTGV0J3MgcmVtb3ZlIHRoZSBgcHJpbnQoIkhlbGxvIFdvcmxkIilgIGFuZCBsb2FkIHRoZSByZWxldmFudCBwYWNrYWdlcy4gSW5zdGFsbCB0aGUgcGFja2FnZXMgaWYgeW91IGhhdmVuJ3QgZG9uZSB0aGF0IHlldC4gCmBgYHtyfQojIFRoaXMgd2lsbCBjb250YWluIHRoZSBzcGF0aWFsIGRhdGEgYW5hbHlzaXMgY29kZSBmb3IgdGhpcyB3b3Jrc2hvcC4KbGlicmFyeShzZikKbGlicmFyeSh0ZXJyYSkKbGlicmFyeSh0aWR5dmVyc2UpCmBgYAoKV2UgbmVlZCBzb21lIHNwYXRpYWwgZGF0YSEgSSBoYXZlIGEgZm9sZGVyIG9mIHNhbXBsZSBkYXRhIGhvc3RlZCBvbiBHaXRIdWIgdGhhdCB5b3UgY2FuIGRvd25sb2FkIGhlcmU6IApodHRwczovL2dpdGh1Yi5jb20vSmF5TWF0c3VzaGliYS9UdXRvcmlhbC1SLVNoaW55LVdlYi1NYXBwaW5nLmdpdC4gWW91IGNhbiBkb3dubG9hZCB0aGUgZmlsZXMgaW5kaXZpZHVhbGx5IGluIHRoZSBgZGF0YWAgZm9sZGVyLCBvciB5b3UgY2FuIGBnaXQgY2xvbmVgIHRoZSB3aG9sZSByZXBvc2l0b3J5ICh3aGljaCBpbmNsdWRlcyBhIHNhbXBsZSBTcGF0aWFsIFNoaW55IEFwcCkuIFRoZSBwcm9jZXNzIGZvciBjbG9uaW5nIHRoZSByZXBvc2l0b3J5IGlzIGFjdHVhbGx5IHRoZSBzYW1lIGFzIGxpbmtpbmcgdG8gYSBuZXcgcHJvamVjdCwgc28gZm9sbG93IHRoZSBzdGVwcyBpbiB0aGUgKipDcmVhdGUgbmV3IFIgUHJvamVjdCB0aGF0IGlzIGxpbmtlZCB0byBHaXRIdWIgcmVwb3NpdG9yeSoqIHNlY3Rpb24gYWdhaW4sIGp1c3Qgd2l0aCB0aGUgbGluayB0byBteSByZXBvIGluc3RlYWQuIENvcHkgdGhlIGBkYXRhYCBmb2xkZXIgZnJvbSBteSByZXBvc2l0b3J5IGludG8geW91ciBvd24gcHJvamVjdC4gCgpDb21tb24gZmlsZSBmb3JtYXRzIGZvciBzcGF0aWFsIGRhdGEgaW5jbHVkZSBzaGFwZWZpbGVzICguc2hwIHdpdGggYXNzb2NpYXRlZCBmaWxlcyksIC5nZW9qc29uLCAuZ2VvdGlmZiwgZXRjLiAodGhlcmUgYXJlIHNvIG1hbnkuLi4pLiBHZW5lcmFsbHkgdGhvdWdoLCB3ZSBjYW4gcmVhZCBpbiB2ZWN0b3Igc3BhdGlhbCBkYXRhIHdpdGggYHN0X3JlYWQoImZpbGVwYXRoX2hlcmUiKWAgYW5kIHJhc3RlciBkYXRhIHdpdGggYHJhc3QoImZpbGVwYXRoX2hlcmUiKWAuIAoKIyMjIExldCdzIHRyeSBpbXBvcnRpbmcgc29tZSBzcGF0aWFsIGZpbGVzIGludG8gUjoKYGBge3J9CiMgVGhlc2UgYXJlIGlOYXR1cmFsaXN0IG9ic2VydmF0aW9ucyBvZiB0aGUgQW1lcmljYW4gQmxhY2sgQmVhciBpbiBCcml0aXNoIENvbHVtYmlhCiMgVGhpcyBpcyBhbiBleGFtcGxlIG9mIGEgcG9pbnQgZGF0YS4KYmxhY2tfYmVhcl9vYnNlcnZhdGlvbnNfYmMgPC0gc3RfcmVhZCgiZGF0YS9ibGFja19iZWFyX29ic2VydmF0aW9uc19iYy9ibGFja19iZWFyX29ic2VydmF0aW9uc19iYy5zaHAiKQoKIyBUaGlzIGlzIGEgcG9seWdvbiByZXByZXNlbnRpbmcgdGhlIGJvdW5kYXJpZXMgb2YgQnVybmFieQojIGh0dHBzOi8vZGF0YS5idXJuYWJ5LmNhL2RhdGFzZXRzL2Q5MDNiODc3ODI3MzQ0NTFhZTI4NmNiMGI1OTkzOGFjCmJ1cm5hYnkgPC0gc3RfcmVhZCgiZGF0YS9CdXJuYWJ5X0JvdW5kYXJ5L0J1cm5hYnlfQm91bmRhcnkuc2hwIikKCiMgVGhpcyBpcyBhIG11bHRpcG9seWdvbiByZXByZXNlbnRpbmcgQnJpdGlzaCBDb2x1bWJpYQojIGh0dHBzOi8vb3Blbi5jYW5hZGEuY2EvZGF0YS9lbi9kYXRhc2V0L2E4ODNlYjE0LTBjMGUtNDVjNC1iOGM0LWI1NGM0YTgxOWVkYiAKYnJpdGlzaF9jb2x1bWJpYSA8LSBzdF9yZWFkKCJkYXRhL2JyaXRpc2hfY29sdW1iaWEvYnJpdGlzaF9jb2x1bWJpYS5zaHAiKQpgYGAKCiMjIyBMZXQncyB0cnkgZG9pbmcgc29tZSBiYXNpYyBwbG90cyAKVGhlIHNwYXRpYWwgaW5mb3JtYXRpb24gaXMgc2F2ZWQgaW4gdGhlIGBnZW9tZXRyeWAgY29sdW1uIG9mIHRoZXNlIG9iamVjdHMsIHNvIHdlIGNhbiBhY2Nlc3MgdGhlbSB0aGlzIHdheTogCmBgYHtyfQpwbG90KGJsYWNrX2JlYXJfb2JzZXJ2YXRpb25zX2JjJGdlb21ldHJ5KQoKcGxvdChidXJuYWJ5JGdlb21ldHJ5KQoKcGxvdChicml0aXNoX2NvbHVtYmlhJGdlb21ldHJ5KQpgYGAKCiMjIyBJbnRlcnNlY3Rpb24gClVzaW5nIHRoZSBgc3RfaW50ZXJzZWN0aW9uKClgIGZ1bmN0aW9uLCB3ZSBjYW4gc2VsZWN0IHRoZSBwb2ludHMgKHkpIHRoYXQgYXJlIHdpdGhpbiBhbiBhcmVhICh4KS4gCmBgYHtyfQpibGFja19iZWFyX29ic2VydmF0aW9uc19idXJuYWJ5IDwtIHN0X2ludGVyc2VjdGlvbih4ID0gYnVybmFieSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBibGFja19iZWFyX29ic2VydmF0aW9uc19iYykKYGBgCldlIGNhbiBhY3R1YWxseSB1c2UgZ2dwbG90IHRvIHB1dCBtdWx0aXBsZSBzcGF0aWFsIGxheWVycyBvbnRvIG9uZSBwbG90LiBPbmx5IHRoZSBibGFjayBiZWFyIG9ic2VydmF0aW9ucyB3aXRoaW4gQnVybmFieSBhcmUgcGxvdHRlZCwgc2luY2Ugd2UgdXNlZCB0aGUgYHN0X2ludGVyc2VjdGlvbigpYCB0byBjbGlwIHRoZSBwb2ludHMgdG8gdGhlIEJ1cm5hYnkgYm91bmRhcnkuICAKYGBge3J9CmdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSBidXJuYWJ5KSArCiAgZ2VvbV9zZihkYXRhID0gYmxhY2tfYmVhcl9vYnNlcnZhdGlvbnNfYnVybmFieSkKYGBgCgojIyMgQ2xpcApVc2luZyB0aGUgc2FtZSBgc3RfaW50ZXJzZWN0aW9uKClgIGJldHdlZW4gdHdvIHBvbHlnb24gbGF5ZXJzIHdpbGwgYWN0IGFzIGEgY2xpcCwgcmV0dXJuaW5nIHRoZSBvdmVybGFwcGluZyBwYXJ0IG9mIHRoZSB0d28gcG9seWdvbnMuICAKYGBge3IsIGVycm9yID0gVFJVRX0KYmNfd2l0aG91dF9idXJuYWJ5IDwtIHN0X2ludGVyc2VjdGlvbihidXJuYWJ5LCBicml0aXNoX2NvbHVtYmlhKQpgYGAKT2ggbm8hIEFuIGVycm9yISAKClRoZSBgc3RfY3JzKClgIGZ1bmN0aW9uIHJldHVybnMgdGhlIGNvb3JkaW5hdGUgc3lzdGVtIG9mIHRoZSBzcGF0aWFsIG9iamVjdC4gVGhpcyBlcnJvciBtZWFucyB0aGF0IHRoZSB0d28gaW5wdXQgc3BhdGlhbCBvYmplY3RzIGFyZSBub3QgdGhlIHNhbWUgY29vcmRpbmF0ZSBzeXN0ZW1zIGFuZCB0aGVyZWZvcmUgdGhlIGBzdF9pbnRlcnNlY3Rpb24oKWAgZG9lc24ndCB3b3JrLiAKCldlIGNhbiBjaGVjayB0aGUgY29vcmRpbmF0ZSBzeXN0ZW1zIGZvciBlYWNoIHRvIHZlcmlmeToKYGBge3J9CnN0X2NycyhidXJuYWJ5KSAjIENvb3JkaW5hdGUgc3lzdGVtIGlzIFdHUyA4NApzdF9jcnMoYnJpdGlzaF9jb2x1bWJpYSkgIyBDb29yZGluYXRlIHN5c3RlbSBpcyBOQUQ4MyAvIFN0YXRpc3RpY3MgQ2FuYWRhIExhbWJlcnQKYGBgCkluIG9yZGVyIHRvIHJlbWVkeSB0aGVzZSBpc3N1ZXMsIHdlIGNhbiB0cmFuc2Zvcm0gb25lIG9mIHRoZSBsYXllcnMgdG8gbWF0Y2ggdGhlIG90aGVyLiAKYGBge3J9CmJ1cm5hYnlfdHJhbnNmb3JtZWQgPC0gc3RfdHJhbnNmb3JtKGJ1cm5hYnksIHN0X2Nycyhicml0aXNoX2NvbHVtYmlhKSkKYnVybmFieV9iY19jbGlwIDwtIHN0X2ludGVyc2VjdGlvbihidXJuYWJ5X3RyYW5zZm9ybWVkLCBicml0aXNoX2NvbHVtYmlhKQpgYGAKCk5vdyB3ZSBjYW4gbG9vayBhdCB3aGF0IHRoaXMgbG9va3MgbGlrZSB0byBjbGlwIEJyaXRpc2ggQ29sdW1iaWEgdG8gQnVybmFieS4gV2hpbGUgaXQgbG9va3MgZGlmZmVyZW50IGR1ZSB0byB0aGUgdHJhbnNmb3JtYXRpb24sIHRoaXMgZW5kcyB1cCBqdXN0IGJlaW5nIHRoZSBCdXJuYWJ5IGJvdW5kYXJ5IGFyZWEgYmVjYXVzZSBCdXJuYWJ5IGlzIGVudGlyZWx5IGNvbnRhaW5lZCBieSBCcml0aXNoIENvbHVtYmlhLiAKYGBge3J9CmdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSBidXJuYWJ5X2JjX2NsaXApCmBgYAoKIyMjIEJ1ZmZlcgpMZXQncyB0cnkgbWFraW5nIHNvbWUgYnVmZmVycyBhcm91bmQgQnVybmFieS4gCmBgYHtyfQojIFRoZSBzZWNvbmQgcGFyYW1ldGVyIGlzIHRoZSBkaXN0YW5jZSBvZiB0aGUgYnVmZmVyCiMgVGhlIHVuaXRzIGRlcGVuZCBvbiB0aGUgY29vcmRpbmF0ZSByZWZlcmVuY2Ugc3lzdGVtIG9mIHRoZSBpbnB1dCBsYXllci4gCmJ1cm5hYnlfYnVmZmVyIDwtIHN0X2J1ZmZlcihidXJuYWJ5X3RyYW5zZm9ybWVkLCAyMDAwKSAKCmdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSBidXJuYWJ5X2J1ZmZlcikgKwogICAgZ2VvbV9zZihkYXRhID0gYnVybmFieSwgZmlsbCA9ICJncmV5IikgCmBgYAoKIyMjIENvdW50cwpXZSBjYW4gY291bnQgdGhlIG51bWJlciBvZiBwb2ludHMgdGhhdCBmYWxsIGludG8gZWFjaCBhcmVhIGJ5IHVzaW5nIGBzdF9pbnRlcnNlY3RzKClgLiBUaGlzIHJldHVybnMgc29tZXRoaW5nIGRpZmZlcmVudCBjb21wYXJlZCB0byBgc3RfaW50ZXJzZWN0aW9uKClgLCBhcyBpdCByZXR1cm5zIHRoZSBsaXN0IG9mIGluZGV4ZXMgb2YgdGhlIGludGVyc2VjdGluZyByb3dzIGluIHRoZSBzZWNvbmQgb2JqZWN0LiAgCmBgYHtyfQppbmRleCA8LSBzdF9pbnRlcnNlY3RzKGJ1cm5hYnksIGJsYWNrX2JlYXJfb2JzZXJ2YXRpb25zX2JjKQpgYGAKU28gZm9yIGEgc2ltcGxlIGNvdW50IG9mIGEgc2luZ2xlIHBvbHlnb24sIHdlIGNvdWxkIGp1c3QgdGFrZSB0aGUgbGVuZ3RoIG9mIHRoaXMgbGlzdC4gCmBgYHtyfQpsZW5ndGhzKGluZGV4KQpgYGAKTGV0J3MgZG8gc29tZXRoaW5nIG1vcmUgaW50ZXJlc3Rpbmcgd2l0aCB0aGlzLCBhbmQgZ2V0IHRoZSBjb3VudHMgb2Ygb2JzZXJ2YXRpb25zIGFjcm9zcyBCcml0aXNoIENvbHVtYmlhIG92ZXIgYSByZWd1bGFyIGdyaWQgY2VsbC4gV2UgbmVlZCB0byBzdGFydCB3aXRoIGNyZWF0aW5nIHRoZSBncmlkIGNlbGxzIHVzaW5nIGBzdF9tYWtlX2dyaWQoKWAuIApgYGB7cn0KZ3JpZCA8LSBzdF9tYWtlX2dyaWQoCiAgYnJpdGlzaF9jb2x1bWJpYSwKICBjZWxsc2l6ZSA9IDUwMDAwCikKCiMgV2UgY2FuIHNlZSB0aGF0IHdlIGNyZWF0ZWQgYSByZWd1bGFyIHNpemVkIGdyaWQgb3ZlciB0aGUgYXJlYSBvZiBCQy4gCmdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSBncmlkKSArCiAgZ2VvbV9zZihkYXRhID0gYnJpdGlzaF9jb2x1bWJpYSkKYGBgCldlIGNhbiBkbyB0aGUgc2FtZSBgc3RfaW50ZXJzZWN0cygpYCBvcGVyYXRpb24gYmV0d2VlbiB0aGlzIGdyaWQgYW5kIHRoZSBibGFjayBiZWFyIG9ic2VydmF0aW9ucy4gVGhpcyB3aWxsIHJldHVybiBhIGxpc3Qgb2YgbGlzdHMsIHdpdGggb25lIGxpc3Qgb2Ygcm93IGluZGV4ZXMgb2YgdGhlIGJsYWNrIGJlYXIgb2JzZXJ2YXRpb25zIHRoYXQgaW50ZXJzZWN0IHdpdGggZWFjaCBncmlkIGNlbGwuCmBgYHtyfQpibGFja19iZWFyX29ic2VydmF0aW9uc19iY190cmFuc2Zvcm0gPC0gYmxhY2tfYmVhcl9vYnNlcnZhdGlvbnNfYmMgJT4lCiAgc3RfdHJhbnNmb3JtKHN0X2NycyhncmlkKSkgIyByZW1lbWJlcmluZyB0aGF0IHRoZSBDUlMgbmVlZCB0byBiZSB0aGUgc2FtZSAKCiMgVGhpcyByZXR1cm5zIGEgbGlzdCBvZiBsaXN0cwppbmRleF9ncmlkIDwtIHN0X2ludGVyc2VjdHMoZ3JpZCwgYmxhY2tfYmVhcl9vYnNlcnZhdGlvbnNfYmNfdHJhbnNmb3JtKSAKCiMgVGhpcyB0dXJucyBpbnRvIGEgbGlzdCBvZiBjb3VudHMgb2Ygb2JzZXJ2YXRpb25zIGluIGVhY2ggZ3JpZCBjZWxsIApncmlkX2NvdW50IDwtIGxlbmd0aHMoaW5kZXhfZ3JpZCkKYGBgCgpTaW5jZSBub3cgd2UgaGF2ZSBhIGxpc3Qgb2YgY291bnRzLCB3aGljaCBpcyBvcmRlcmVkIGJhc2VkIG9uIHRoZSBncmlkIGNlbGwgbGF5ZXIsIHdlIGNhbiBzaW1wbHkgY29sdW1uIGJpbmQgdGhlbSB0b2dldGhlci4gV2UgY2FuIHRha2UgdGhhdCBtYXRyaXggYW5kIGNvbnZlcnQgdG8gYSBkYXRhZnJhbWUgZmlyc3QsIHRoZW4gaW50byB0aGUgc3BhdGlhbCBvYmplY3QgKHNmKS4gV2UgcmVuYW1lZCB0aGUgYGdyaWRgIGNvbHVtbiB0byBgZ2VvbWV0cnlgLCBzaW5jZSB0aGF0IGlzIHRoZSBkZWZhdWx0IG5hbWUgb2YgdGhlIGNvbHVtbiB3aXRoIHRoZSBzcGF0aWFsIGluZm9ybWF0aW9uIApgYGB7cn0KIyBjb21iaW5lIGdyaWQgY2VsbHMgYW5kIGNvdW50cwpiZWFyX29ic2VydmF0aW9uX2NvdW50c19ncmlkIDwtIGNiaW5kKGdyaWQsIGdyaWRfY291bnQpICU+JQogIGFzLmRhdGEuZnJhbWUoKSAlPiUgIyBjb252ZXJ0IHRvIGRhdGFmcmFtZSwgdG8gYWxsb3cgY29udmVyc2lvbiB0byBzZgogIHN0X2FzX3NmKCkgJT4lICMgY29udmVydCB0byBzZgogIHN0X3NldF9jcnMoc3RfY3JzKGdyaWQpKSAlPiUgIyBtYWtlIHN1cmUgdGhhdCB0aGUgb3JpZ2luYWwgQ1JTIGlzIGFzc2lnbmVkCiAgcmVuYW1lKGdlb21ldHJ5ID0gZ3JpZCkgJT4lICMgcmVuYW1lIHRvIGRlZmF1bHQgZ2VvbWV0cnkgY29sdW1uIG5hbWUKICBtdXRhdGUoZ3JpZF9jb3VudCA9IGFzLm51bWVyaWMoZ3JpZF9jb3VudCkpCgpnZ3Bsb3QoKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2MoKSArCiAgZ2VvbV9zZihkYXRhID0gYmVhcl9vYnNlcnZhdGlvbl9jb3VudHNfZ3JpZCwgCiAgICAgICAgICBhZXMoZmlsbCA9IGdyaWRfY291bnQpKQpgYGAKCkFuZCB3ZSBjYW4gY2xlYW4gdGhpcyB1cCBhIGJpdCBtb3JlIGJ5IGludGVyc2VjdGluZyBhZ2FpbiB3aXRoIEJyaXRpc2ggQ29sdW1iaWEuIFRoaXMgbWF5IHRha2UgcXVpdGUgYSBsb25nIHRpbWUsIHNvIHdlIGNhbiBza2lwIHRoaXMuIFRoaXMgaXMgYWxzbyBhIHJlYXNvbiB0byB1c2UgYHN0X2ludGVyc2VjdHMoKWAgaW5zdGVhZCBvZiBgc3RfaW50ZXJzZWN0aW9uKClgIHdoZW4gcG9zc2libGUuIApgYGB7cn0KYmVhcl9vYnNfY291bnRzX2dyaWRzX2NsaXAgPC0gc3RfaW50ZXJzZWN0aW9uKGJlYXJfb2JzZXJ2YXRpb25fY291bnRzX2dyaWQsIGJyaXRpc2hfY29sdW1iaWEpCgpnZ3Bsb3QoKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2MoKSArCiAgZ2VvbV9zZihkYXRhID0gYmVhcl9vYnNfY291bnRzX2dyaWRzX2NsaXAsIAogICAgICAgICAgYWVzKGZpbGwgPSBncmlkX2NvdW50KSkKYGBgCgojIyBQYXJ0IDM6IEJhc2ljIFNoaW55IEFwcHMKVG8gZ2V0IHN0YXJ0ZWQsIGxldCdzIGRvIGFuIGludHJvZHVjdGlvbiBvbiBTaGlueSBhcHBzLiBTaGlueSBhcHBzIGFyZSBhIHdheSB0byBjcmVhdGUgaW50ZXJhY3RpdmUsIHdlbGwsIGFwcGxpY2F0aW9ucyB1c2luZyB0aGUgUiBsYW5ndWFnZS4gSW4gb3JkZXIgdG8gY3JlYXRlIFNoaW55IGFwcHMsIHdlIG5lZWQgdG8gZGlzdGluZ3Vpc2ggaG93IHRoZXkgd29yayBpbiBjb250cmFzdCB0byB0aGUgYGludGVyYWN0aXZlYCBtb2RlIHRoYXQgd2UgdXN1YWxseSB1c2UgdG8gd29yayB3aXRoIFIuIAoKSW4gdHlwaWNhbCBSIGNvZGUsIHdlIHJ1biBvdXIgY29kZSBsaW5lIGJ5IGxpbmUuIFRoaXMgaXMgb2Z0ZW4gY2FsbGVkIHNjcmlwdGluZywgYW5kIHdlIGtub3cgb3VyIGNvZGUgcnVucyBmcm9tIHRoZSB0b3Agb2YgdGhlIHBhZ2UgdG8gdGhlIGJvdHRvbS4gV2UgY2FuIGFsc28gcnVuIHRoaXMgc2NyaXB0IG9uZSBsaW5lIGF0IGEgdGltZS4gVGhhdCBtZWFucyB3ZSBjYW4gd3JpdGUgb25lIGxpbmUgb2YgY29kZSBhbmQgcnVuIGl0LgpgYGB7cn0KeCA8LSAiaGVsbG8gd29ybGQiCmBgYAoKVGhlbiB3ZSBjYW4gYWRkIGFub3RoZXIgbGluZSBvZiBjb2RlIGFuZCBydW4gaXQsIGFuZCB0aGlzIHdvdWxkIHdvcmsgY29ycmVjdGx5LiAKYGBge3J9CnByaW50KHgpCmBgYAoKSG93ZXZlciwgd2hlbiB3ZSBjcmVhdGUgU2hpbnkgYXBwcyBhbmQgdGhlbiBydW4gdGhlbSwgd2UgZG9uJ3QgaGF2ZSB0aGUgb3B0aW9uIG9mIGFkZGluZyBuZXcgbGluZXMgb2YgY29kZSBhbmQgYnVpbGRpbmcgdXBvbiB0aGUgcHJldmlvdXMgc3RlcHMgd2hpbGUgdGhlIGFwcCBpcyBydW5uaW5nLiBJbiBvdGhlciB3b3JkcywgdGhlIFNoaW55IGFwcCB3aWxsIG5lZWQgdG8gY29udGFpbiBhbGwgb2YgdGhlIGNvZGUgdG8gbG9hZCBpbiBmaWxlcywgZG8gdGhlIGludGVuZGVkIG9wZXJhdGlvbnMgb24gdGhlbSwgZXRjLiBUaGlzIHdpbGwgYmVjb21lIGFwcGFyZW50IGFzIHdlIGdvIHRocm91Z2ggdGhlIGxhYi4gSW4gYSBTaGlueSBhcHAsIHRoZSBjb2RlIGRvZXMgbm90IHJ1biBmcm9tIHRvcCB0byBib3R0b20gb2YgdGhlIHBhZ2UgZWl0aGVyLCBpbnN0ZWFkIHJlbHlpbmcgb24gc29tZXRoaW5nIGNhbGxlZCBgcmVhY3Rpdml0eWAgZm9yIGRlZmluaW5nIHdoaWNoIGNvZGUgc2hvdWxkIHJ1biBhbmQgd2hlbi4gCgpUaGlzIHBhcnQgcmVmZXJlbmNlcyBNYXN0ZXJpbmcgU2hpbnkgKGh0dHBzOi8vbWFzdGVyaW5nLXNoaW55Lm9yZy9pbmRleC5odG1sKS4gUGxlYXNlIHRha2UgYSBsb29rIHRoZXJlIGZvciBhZGRpdGlvbmFsIGNvbnRleHQgYW5kIGxlYXJuaW5nLiAgCgojIyMgQmFzaWMgU3RydWN0dXJlIG9mIGEgU2hpbnkgQXBwCkxldCdzIGNyZWF0ZSBhIG5ldyBmaWxlIGNhbGxlZCBgYXBwLlJgLiBUaGlzIHNpbmdsZSBmaWxlIHdpbGwgY29udGFpbiBhbGwgb2YgdGhlIGNvZGUgd2UgbmVlZCBmb3Igb3VyIFNoaW55IGFwcCBmb3Igbm93LiBUaGUgY29kZSBiZWxvdyBoYXMgdGhlIGJhc2ljIHN0cnVjdHVyZSBvZiBhIFNoaW55IEFwcCwgYW5kIGlzIHByZXR0eSBtdWNoIHRoZSBzaW1wbGVzdCBTaGlueSBBcHAgdGhhdCB5b3UgY2FuIGNyZWF0ZS4gCmBgYHtyLCBldmFsPUZBTFNFfQojIGxvYWQgdGhlIHNoaW55IGxpYnJhcnkKbGlicmFyeShzaGlueSkKCiMgVGhpcyBkZWZpbmVzIHRoZSB1c2VyIGludGVyZmFjZSwgd2hpY2ggaXMgdGhlIHBhcnQgdGhhdCB5b3VyIHVzZXJzIHdpbGwgc2VlIGFuZCBpbnRlcmFjdCB3aXRoCnVpIDwtIGZsdWlkUGFnZSgKICAiSGVsbG8sIHdvcmxkISIKKQoKIyBUaGlzIGRlZmluZXMgdGhlIGJlaGF2aW91ciBvZiB0aGUgYXBwLiAKIyBDdXJyZW50bHksIHRoaXMgaXMgZW1wdHkgc28gb3VyIGFwcGxpY2F0aW9uIGRvZXNuJ3QgZG8gYW55dGhpbmcuIApzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCwgc2Vzc2lvbikgewp9CgojIFRoaXMgbGluZSBleGVjdXRlcyBvciBzdGFydHMgdGhlIFNoaW55IGFwcApzaGlueUFwcCh1aSwgc2VydmVyKQpgYGAKCllvdSBzaG91bGQgYmUgYWJsZSB0byBjbGljayBgUnVuIEFwcGAgYWJvdmUgdGhlIGFwcC5SIHRoYXQgaXMgb3Blbi4gCiFbU2NyZWVuc2hvdCBmb3IgdGhlIFJ1biBBcHAgYnV0dG9uXShpbWFnZXMvcnVuYXBwLnBuZykgCgpUaGlzIHNob3VsZCBvcGVuIGEgbmV3IHdpbmRvdyB0aGF0IHNob3dzIHlvdXIgYXBwbGljYXRpb24uIAohW1NjcmVlbnNob3QgZm9yIHRoZSBIZWxsbyBXb3JsZCEgYXBwXShpbWFnZXMvaGVsbG93b3JsZF9zaGlueS5wbmcpCgojIyMgQmFzaWMgVUkgCk5vdyB0aGlzIFNoaW55IGFwcCBpcyBwcmV0dHkgYm9yaW5nLCB3aXRoIG5vIGludGVyYWN0aXZpdHkgZm9yIHRoZSB1c2VyLiBMZXQncyBhZGQgc29tZSBVSSBlbGVtZW50cyB0byB0aGUgYXBwLiBUaGVyZSBhcmUgYSBudW1iZXIgb2YgcHJlbWFkZSBVSSBlbGVtZW50cyB0aGF0IHdlIHdpbGwgdXNlIChTZWUgaHR0cHM6Ly9tYXN0ZXJpbmctc2hpbnkub3JnL2Jhc2ljLXVpLmh0bWwgZm9yIG1vcmUgZGV0YWlscyBvbiB3aGF0IGlzIGF2YWlsYWJsZSkuIFdlIHVuZm9ydHVuYXRlbHkgd29uJ3QgaGF2ZSB0aW1lIHRvIGdvIG92ZXIgdGhlIHJhbmdlIG9mIFVJIHBvc3NpYmxlIHdpdGggU2hpbnksIGFzIHRoYXQgY291bGQgYmUgYSB3aG9sZSB3b3Jrc2hvcCBpbiBpdHNlbGYuCgojIyMjIFNhbXBsZSBTaGlueSBBcHAKCiMjIyMgSW5wdXRzCkxldCdzIGxvb2sgYXQgVUkgcGFydCBvZiBgYXBwLlJgIGFnYWluIGFuZCBhZGQgYSBjb3VwbGUgbGluZXMgb2YgY29kZSB0byBiZWdpbiBjcmVhdGluZyB0aGUgVUkuIExldCdzIGNsZWFyIHRoZSBgIkhlbGxvIFdvcmxkImAsIGFuZCBjcmVhdGUgc29tZSBpbnB1dHMuIFdlJ2xsIHN0YXJ0IHdpdGggYSByZWFsbHkgc2ltcGxlIHRleHQgaW5wdXQgYW5kIG51bWVyaWMgaW5wdXQuIFRoZXNlIGlucHV0cyBhcmUgaG93IHdlIGdldCB0aGUgdXNlcidzIGlucHV0IHRvIHRyaWdnZXIgb3IgY2hhbmdlIHNvbWUgYmVoYXZpb3VyIG9mIHRoZSBhcHAuIAoKYGBge3IsIGV2YWw9RkFMU0V9CiMgVGhpcyBkZWZpbmVzIHRoZSB1c2VyIGludGVyZmFjZSwgd2hpY2ggaXMgdGhlIHBhcnQgdGhhdCB5b3VyIHVzZXJzIHdpbGwgc2VlIGFuZCBpbnRlcmFjdCB3aXRoCnVpIDwtIGZsdWlkUGFnZSgKICB0ZXh0SW5wdXQoIm5hbWUiLCAiV2hhdCBpcyB5b3VyIG5hbWU/IiksCiAgbnVtZXJpY0lucHV0KCJudW1iZXIiLCAiRW50ZXIgYSBudW1iZXIiLCAKICAgICAgICAgICAgICAgdmFsdWUgPSAxMiwKICAgICAgICAgICAgICAgbWluID0gMCwgCiAgICAgICAgICAgICAgIG1heCA9IDIwMCkKICBudW1lcmljSW5wdXQoaW5wdXRJZCA9ICJudW1iZXIyIiwKICAgICAgICAgICAgICAgbGFiZWwgPSAiRW50ZXIgYSBudW1iZXIiLAogICAgICAgICAgICAgICB2YWx1ZSA9IDEyLAogICAgICAgICAgICAgICBtaW4gPSAwLAogICAgICAgICAgICAgICBtYXggPSAyMDApCikKYGBgCiFbQmFzaWMgVUldKGltYWdlcy9iYXNpY191aS5wbmcpCgpMZXQncyBhbHNvIGFkZCBhIGJ1dHRvbiwgd2hpY2ggd2UgY2FuIGxhdGVyIHVzZSB0byB0cmlnZ2VyIHNvbWUgYWN0aW9ucyBmb3IgdGhlIGFwcC4gCmBgYHtyLCBldmFsPUZBTFNFfQojIFRoaXMgZGVmaW5lcyB0aGUgdXNlciBpbnRlcmZhY2UsIHdoaWNoIGlzIHRoZSBwYXJ0IHRoYXQgeW91ciB1c2VycyB3aWxsIHNlZSBhbmQgaW50ZXJhY3Qgd2l0aAp1aSA8LSBmbHVpZFBhZ2UoCiAgdGV4dElucHV0KGlucHV0SWQgPSAibmFtZSIsIAogICAgICAgICAgICBsYWJlbCA9ICJXaGF0IGlzIHlvdXIgbmFtZT8iKSwKICBudW1lcmljSW5wdXQoaW5wdXRJZCA9ICJudW1iZXIiLCAKICAgICAgICAgICAgICAgbGFiZWwgPSAiRW50ZXIgYSBudW1iZXIiLCAKICAgICAgICAgICAgICAgdmFsdWUgPSAxMiwKICAgICAgICAgICAgICAgbWluID0gMCwgCiAgICAgICAgICAgICAgIG1heCA9IDIwMCksCiAgbnVtZXJpY0lucHV0KGlucHV0SWQgPSAibnVtYmVyMiIsCiAgICAgICAgICAgICAgIGxhYmVsID0gIkVudGVyIGEgbnVtYmVyIiwKICAgICAgICAgICAgICAgdmFsdWUgPSAxMiwKICAgICAgICAgICAgICAgbWluID0gMCwKICAgICAgICAgICAgICAgbWF4ID0gMjAwKSwKICBhY3Rpb25CdXR0b24oaW5wdXRJZCA9ICJjbGljayIsIAogICAgICAgICAgICAgICBsYWJlbCA9ICJDbGljayBNZSEiKQopCmBgYAohW0Jhc2ljIFVJIHdpdGggYnV0dG9uXShpbWFnZXMvYmFzaWNfdWlfd2l0aF9idXR0b24ucG5nKQoKTm90ZSB0aGF0IGV2ZXJ5IGlucHV0IGhhcyBhbiBgaW5wdXRJZGAgcGFyYW1ldGVyLiBUaGVzZSBoYXZlIHRvIGJlIHVuaXF1ZSB2YWx1ZXMgYWNyb3NzIGFsbCB0aGUgVUkgZWxlbWVudHMsIGJlY2F1c2UgdGhpcyBpcyBob3cgd2Ugd2lsbCBhY2Nlc3MgdGhlIHZhbHVlcyBpbnB1dHRlZCBpbnRvIHRob3NlIFVJIGVsZW1lbnRzLiAKCk5vdywgd2UgY2FuIGFjY2VzcyB0aGUgaW5wdXQgdmFsdWVzIGluIHRoZSBzZXJ2ZXIgZnVuY3Rpb24gYnkgdGhlIGBpbnB1dGAgb2JqZWN0LiBGb3IgZXhhbXBsZSwgd2UgY2FuIGFjY2VzcyB0aGUgYG5hbWVgIGlucHV0SWQgb2JqZWN0IGluIHRoZSBzZXJ2ZXIgZnVuY3Rpb24gYnkgdXNpbmcgYGlucHV0JG5hbWVgLiAKCiMjIyMgT3V0cHV0cwpPdXRwdXRzIGFyZSB2YWx1ZXMgdGhhdCBhcmUgY2FsY3VsYXRlZCBvciByZXRyaWV2ZWQgYnkgdGhlIHNlcnZlciBpbiBzb21lIGZ1bmN0aW9uLCB3aGljaCBhcmUgdGhlbiBkaXNwbGF5ZWQgc29tZWhvdyBvbiB0aGUgVUkuIFNpbWlsYXJseSB0byBpbnB1dHMsIG91dHB1dHMgd2lsbCBoYXZlIGEgdW5pcXVlIGBvdXRwdXRJZGAgcGFyYW1ldGVyLiAKCkxldCdzIHJldmlldyB0aGUgY29tcGxldGUgYXBwIGNvZGUgYXQgdGhpcyBzdGFnZS4gSWYgd2UgcnVuIHRoaXMgYXBwLCB3ZSBzZWUgc29tZSB3YXlzIHRvIGlucHV0IGRhdGEgYnV0IGl0IGRvZXNuJ3Qgc2VlbSB0byBkbyBhbnl0aGluZy4gVGhpcyBpcyBiZWNhdXNlIHdlIGFyZSBtaXNzaW5nIG91dHB1dHMgYW5kIHRoZSBzZXJ2ZXIgY29kZSB0aGF0IGRlc2NyaWJlcyB0aGUgbG9naWMgb2YgdGhlIGFwcC4gCmBgYHtyLCBldmFsPUZBTFNFfQpsaWJyYXJ5KHNoaW55KQp1aSA8LSBmbHVpZFBhZ2UoCiAgdGV4dElucHV0KGlucHV0SWQgPSAibmFtZSIsIAogICAgICAgICAgICBsYWJlbCA9ICJXaGF0IGlzIHlvdXIgbmFtZT8iKSwKICBudW1lcmljSW5wdXQoaW5wdXRJZCA9ICJudW1iZXIiLCAKICAgICAgICAgICAgICAgbGFiZWwgPSAiRW50ZXIgYSBudW1iZXIiLCAKICAgICAgICAgICAgICAgdmFsdWUgPSAxMiwKICAgICAgICAgICAgICAgbWluID0gMCwgCiAgICAgICAgICAgICAgIG1heCA9IDIwMCksCiAgbnVtZXJpY0lucHV0KGlucHV0SWQgPSAibnVtYmVyMiIsCiAgICAgICAgICAgICAgIGxhYmVsID0gIkVudGVyIGEgbnVtYmVyIiwKICAgICAgICAgICAgICAgdmFsdWUgPSAxMiwKICAgICAgICAgICAgICAgbWluID0gMCwKICAgICAgICAgICAgICAgbWF4ID0gMjAwKSwKICBhY3Rpb25CdXR0b24oaW5wdXRJZCA9ICJjbGljayIsIAogICAgICAgICAgICAgICBsYWJlbCA9ICJDbGljayBNZSEiKQopCnNlcnZlciA8LSBmdW5jdGlvbihpbnB1dCwgb3V0cHV0LCBzZXNzaW9uKSB7Cn0Kc2hpbnlBcHAodWksIHNlcnZlcikKYGBgCgpIZXJlLCB3ZSB3aWxsIGFkZCBhIGNvdXBsZSBtb3JlIGxpbmVzIHRvIHRoZSBVSSBvZiB0aGUgYXBwIGZvciB0aGUgb3V0cHV0cy4gVGhlc2UgYXJlIHR3byBtb3N0IHNpbXBsZSBvdXRwdXRzLCB3aXRoIGB0ZXh0T3V0cHV0KClgIGZvciByZWd1bGFyIHRleHQgYW5kIGB2ZXJiYXRpbVRleHRPdXRwdXQoKWAgZm9yIGNvZGUgLyBjb25zb2xlIG91dHB1dHMuIApgYGB7ciwgZXZhbD1GQUxTRX0KbGlicmFyeShzaGlueSkKdWkgPC0gZmx1aWRQYWdlKAogIHRleHRJbnB1dChpbnB1dElkID0gIm5hbWUiLAogICAgICAgICAgICBsYWJlbCA9ICJXaGF0IGlzIHlvdXIgbmFtZT8iKSwKICBudW1lcmljSW5wdXQoaW5wdXRJZCA9ICJudW1iZXIiLAogICAgICAgICAgICAgICBsYWJlbCA9ICJFbnRlciBhIG51bWJlciIsCiAgICAgICAgICAgICAgIHZhbHVlID0gMTIsCiAgICAgICAgICAgICAgIG1pbiA9IDAsCiAgICAgICAgICAgICAgIG1heCA9IDIwMCksCiAgbnVtZXJpY0lucHV0KGlucHV0SWQgPSAibnVtYmVyMiIsCiAgICAgICAgICAgICAgIGxhYmVsID0gIkVudGVyIGEgbnVtYmVyIiwKICAgICAgICAgICAgICAgdmFsdWUgPSAxMiwKICAgICAgICAgICAgICAgbWluID0gMCwKICAgICAgICAgICAgICAgbWF4ID0gMjAwKSwKICBhY3Rpb25CdXR0b24oaW5wdXRJZCA9ICJjbGljayIsCiAgICAgICAgICAgICAgIGxhYmVsID0gIkNsaWNrIE1lISIpLAogIHRleHRPdXRwdXQob3V0cHV0SWQgPSAidGV4dCIpLAogIHZlcmJhdGltVGV4dE91dHB1dChvdXRwdXRJZCA9ICJjb2RlIikKKQpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCwgc2Vzc2lvbikgewp9CnNoaW55QXBwKHVpLCBzZXJ2ZXIpCmBgYAohW0Jhc2ljIFVJIHdpdGggYnV0dG9uXShpbWFnZXMvYmFzaWNfdWlfd2l0aF9idXR0b24ucG5nKQoKTm93LCBpZiB3ZSBydW4gdGhpcyBhcHAsIHN0aWxsIG5vdGhpbmcgd291bGQgbG9vayBkaWZmZXJlbnQgZnJvbSB0aGUgcHJldmlvdXMgc3RlcHMsIHNpbmNlIHdlIGhhdmVuJ3QgcHJvdmlkZWQgYW55IHNlcnZlciBsb2dpYyBzbyBmYXIuIExldCdzIGFkZCBzb21lIHNpbXBsZSBwbGFjZWhvbGRlciB2YWx1ZXMgaW50byB0aGUgc2VydmVyIGZ1bmN0aW9uLiBTaW1pbGFyIHRvIGlucHV0cywgd2UgY2FuIGFjY2VzcyBvdXRwdXQgdmFyaWFibGVzIHVzaW5nIGBvdXRwdXQkdGhlX291dHB1dElkX29mX291dHB1dF9VSWAuIApgYGB7ciwgZXZhbD1GQUxTRX0Kc2VydmVyIDwtIGZ1bmN0aW9uKGlucHV0LCBvdXRwdXQsIHNlc3Npb24pIHsKICBvdXRwdXQkdGV4dCA8LSByZW5kZXJUZXh0KHsKICAgICJIZWxsbyBmcmllbmQhIgogIH0pCiAgb3V0cHV0JGNvZGUgPC0gcmVuZGVyUHJpbnQoewogICAgNSAqIDIwCiAgfSkKfQpgYGAKIVtCYXNpYyBvdXRwdXRzXShpbWFnZXMvYmFzaWNfb3V0cHV0LnBuZykKCk5vdGUgdGhhdCB3ZSBjYW4ndCBqdXN0IHBhc3MgYSBzdHJpbmcgb3IgYSBidW5jaCBvZiBjb2RlIHRvIGVhY2ggb2YgdGhlc2Ugb3V0cHV0cy4gV2UgbmVlZCB0byB3cmFwIHRoZW0gd2l0aCBhIGByZW5kZXJUZXh0KClgIG9yIGByZW5kZXJQcmludCgpYC4gVGhlc2UgZnVuY3Rpb25zIGNvcnJlc3BvbmQgdG8gdGhlIHRoZSB0eXBlIG9mIG91dHB1dCBVSSB0aGV5IGFyZSBsaW5raW5nIHRvLiBMYXRlciwgd2Ugd291bGQgdXNlIGByZW5kZXJMZWFmbGV0KClgIGluIG9yZGVyIHRvIHJlbmRlciBtYXBzLiBUaGlzIGByZW5kZXJgIGZ1bmN0aW9uIGlzIG5lY2Vzc2FyeSBmb3IgZW5hYmxpbmcgdGhlIG91dHB1dCBVSSBlbGVtZW50cyB0byB1cGRhdGUgd2hlbiBpdCdzIGlucHV0cyBhcmUgY2hhbmdlZCAoKipyZWFjdGl2aXR5KiopLCBhbmQgZm9yIHRyYW5zbGF0aW5nIFIgY29kZSBpbnRvIEhUTUwgKHdoaWNoIGlzIHRoZSBsYW5ndWFnZSBmb3IgdGhlIHdlYiBwYWdlKS4gCgpBdCB0aGlzIHBvaW50LCB3ZSBoYXZlIHRoZSBiYXNpYyBpbnB1dHMgYW5kIG91dHB1dHMgbmVjZXNzYXJ5IHRvIGNyZWF0ZSBvdXIgYXBwLCBidXQgb3VyIGFwcCBkb2Vzbid0IGRvIGFueXRoaW5nLiBUaGlzIGlzIHdoZXJlIHdlIG5lZWQgdG8gdW5kZXJzdGFuZCB0aGUgY29uY2VwdCBvZiAqKnJlYWN0aXZpdHkqKiBhcyB1c2VkIGJ5IFNoaW55LiAKCiMjIyBCYXNpYyBSZWFjdGl2aXR5ClNoaW55IGFwcHMgdXNlIHRoaXMgY29uY2VwdCBjYWxsZWQgKipyZWFjdGl2aXR5KiosIHdoaWNoIHVuZGVycGlucyB0aGVpciBiZWhhdmlvdXIuIFRoaXMgY2FuIGJlIHByZXR0eSB0b3VnaCB0byB3cmFwIHlvdXIgaGVhZCBhcm91bmQsIHNpbmNlIGl0IGlzIHJlYWxseSBkaWZmZXJlbnQgZnJvbSBzY3JpcHRpbmcuIEFzIGRlc2NyaWJlZCBlYXJsaWVyLCBhIHR5cGljYWwgUiBzY3JpcHQgd2lsbCBydW4gdGhlIGNvZGUgZ2VuZXJhbGx5IGluIG9yZGVyLCBmcm9tIHRvcCB0byBib3R0b20uIFdpdGggU2hpbnkgYXBwcywgdGhlIG9yZGVyIGluIHdoaWNoIGNvZGUgaXMgcnVuIGRvZXMgbm90IGZvbGxvdyB0aGlzIHN0cnVjdHVyZS4gCgpJbnN0ZWFkLCBjb2RlIGlzIHJ1biBkZXBlbmRpbmcgb24gdGhlIGNoYW5nZXMgdG8gdGhlIGBpbnB1dGAgb2JqZWN0LCB3aGljaCBpcyBkZWZpbmVkIGJ5IHRoZSB1c2VyJ3MgYWN0aW9ucy4gQXMgb3RoZXIgdmFyaWFibGVzIHRha2UgdGhlIGBpbnB1dGAgYXMgYSBkZXBlbmRlbmN5LCB0aGV5IHdpbGwgYWxzbyBnZXQgdXBkYXRlZCB3aGVuIGBpbnB1dGAgaXMgdXBkYXRlZC4gYG91dHB1dGAgaXMgcmVhbGx5IHNpbWlsYXIsIGJ1dCBpbiB0aGUgb3Bwb3NpdGUgZGlyZWN0aW9uIHdoZXJlIHdlIGNoYW5nZSB0aGUgYG91dHB1dGAgb2JqZWN0IGFuZCB0aG9zZSBjaGFuZ2VzIHByb3BhZ2F0ZSB0byB0aGUgb3V0cHV0IFVJIGVsZW1lbnRzLiAKCkxldCdzIHRyeSBhcHBseWluZyAqKnJlYWN0aXZpdHkqKiB0byBvdXIgYGFwcC5SYC4gTGV0J3Mgc2ltcGxpZnkgb3VyIGNvZGUgZm9yIG5vdywgYW5kIGZvY3VzIG9uIHRoZSBgdGV4dElucHV0YCBhbmQgYHRleHRPdXRwdXRgIGhlcmUuIElmIHlvdSB0cnkgcnVubmluZyB0aGlzIFNoaW55IGFwcCwgeW91J2xsIG5vdGljZSB0aGF0IHRoZSBgdGV4dE91dHB1dGAgdXBkYXRlcyBldmVyeSB0aW1lIHRoZXJlIGlzIGEgY2hhbmdlIG1hZGUgdG8gdGhlIHVzZXIgaW5wdXQuIFRoaXMgbWVhbnMgdGhlIGByZW5kZXJUZXh0KClgIGlzIHJ1bm5pbmcgZXZlcnkgdGltZSB0aGVyZSBpcyBhIGNoYW5nZSB0byBgaW5wdXQkbmFtZWAuIApgYGB7ciwgZXZhbD1GQUxTRX0KbGlicmFyeShzaGlueSkKdWkgPC0gZmx1aWRQYWdlKAogIHRleHRJbnB1dChpbnB1dElkID0gIm5hbWUiLAogICAgICAgICAgICBsYWJlbCA9ICJXaGF0IGlzIHlvdXIgbmFtZT8iKSwKICB0ZXh0T3V0cHV0KG91dHB1dElkID0gInRleHQiKQopCnNlcnZlciA8LSBmdW5jdGlvbihpbnB1dCwgb3V0cHV0LCBzZXNzaW9uKSB7CiAgb3V0cHV0JHRleHQgPC0gcmVuZGVyVGV4dCh7CiAgICBwYXN0ZTAoIkhlbGxvICIsIGlucHV0JG5hbWUsICIhIikKICB9KQp9CnNoaW55QXBwKHVpLCBzZXJ2ZXIpCmBgYAohW0Jhc2ljIHJlYWN0aXZpdHkgZGVtb25zdHJhdGVkXShpbWFnZXMvYmFzaWNfcmVhY3Rpdml0eS5wbmcpCldlIGNhbiBhbHNvIG1vdmUgdGhlIGNvbnRlbnQgb2YgYHJlbmRlclRleHQoKWAgaW50byBhIHJlYWN0aXZlIGV4cHJlc3Npb24gdXNpbmcgYHJlYWN0aXZlKClgLiBUaGlzIGlzbid0IHJlYWxseSBuZWNlc3NhcnkgaGVyZSwgYnV0IGl0IGlzIG1vcmUgdXNlZnVsIGFzIHlvdXIgYXBwbGljYXRpb25zIGJlY29tZSBtb3JlIGNvbXBsZXguIFNvbWV0aGluZyB0byByZW1lbWJlciBpcyB0aGF0IGByZWFjdGl2ZSgpYCBpcyBhIGZ1bmN0aW9uLCBzbyB3ZSBuZWVkIHRvIHdyaXRlIGBncmVldGluZygpYCBpbiBvcmRlciB0byBhY2Nlc3MgdGhlIHZhbHVlIChpLmUuLCBydW5uaW5nIHRoZSBmdW5jdGlvbiksIHJhdGhlciB0aGFuIGBncmVldGluZ2AuIAoKYGBge3IsIGV2YWw9RkFMU0V9CmxpYnJhcnkoc2hpbnkpCnVpIDwtIGZsdWlkUGFnZSgKICB0ZXh0SW5wdXQoaW5wdXRJZCA9ICJuYW1lIiwKICAgICAgICAgICAgbGFiZWwgPSAiV2hhdCBpcyB5b3VyIG5hbWU/IiksCiAgdGV4dE91dHB1dChvdXRwdXRJZCA9ICJ0ZXh0IikKKQpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCwgc2Vzc2lvbikgewogIGdyZWV0aW5nIDwtIHJlYWN0aXZlKHsKICAgIHBhc3RlMCgiSGVsbG8gIiwgaW5wdXQkbmFtZSwgIiEiKQogIH0pCiAgb3V0cHV0JHRleHQgPC0gcmVuZGVyVGV4dCh7CiAgICBncmVldGluZygpCiAgfSkKfQpzaGlueUFwcCh1aSwgc2VydmVyKQpgYGAKCk5vdywgd2UgbWF5IG5vdCBhbHdheXMgd2FudCByZWFjdGl2ZSBiZWhhdmlvdXIuIFdoaWxlIHRoZSBhcHAgcnVucyBmaW5lIHVwZGF0aW5nIGZvciBldmVyeSBjaGFuZ2Ugb2YgaW5wdXQgZm9yIHNvbWV0aGluZyBzbWFsbCwgbGlrZSBvbmUgc2hvcnQgdGV4dCBzdHJpbmcgcmVwcmVzZW50aW5nIGEgbmFtZSwgaXQgd291bGQgYmUgYSBwcm9ibGVtIGZvciBhIG1vcmUgY29tcGxleCBvcGVyYXRpb24uIEltYWdpbmUgeW91IHdhbnRlZCB0byBydW4gYSBtb2RlbCBvciBzcGF0aWFsIGFuYWx5c2lzIHRoYXQgdGFrZXMgYSBmZXcgbWludXRlcy4gWW91IHdvdWxkIHdhbnQgdG8gbWFrZSBzdXJlIHlvdSBoYXZlIGFsbCB5b3VyIGlucHV0cyBjb3JyZWN0LCBhbmQgdGhlbiB0cmlnZ2VyIHRoZSBmdW5jdGlvbi4gSXQgd291bGQgYmUgdmVyeSBmcnVzdHJhdGluZyBpZiBldmVyeSBpbnB1dCBjaGFuZ2UgYXV0b21hdGljYWxseSByYW4gdGhlIG1vZGVsLiAKCkZvciB0aGlzIHB1cnBvc2UsIHdlIGNhbiB1c2Ugc29tZXRoaW5nIGNhbGxlZCBgZXZlbnRSZWFjdGl2ZSgpYCBhbG9uZyB3aXRoIGFuIGBhY3Rpb25CdXR0b24oKWAuIFRoaXMgaXMgcmVhbGx5IHNpbWlsYXIgdG8gd2hhdCBpcyBpbiB0aGUgcHJldmlvdXMgY2h1bmssIHdpdGggdGhlIG1haW4gZGlmZmVyZW5jZSB0aGF0IGByZWFjdGl2ZSgpYCBoYXMgYmVlbiByZXBsYWNlZCBieSBgZXZlbnRSZWFjdGl2ZSgpYC4gVGhlIGZpcnN0IHBhcmFtZXRlciBvZiBgZXZlbnRSZWFjdGl2ZSgpYCBpcyB0aGUgaW5wdXQgYWN0aW9uIHRoYXQgdHJpZ2dlcnMgdGhlIGNoYW5nZTsgaW4gb3VyIGNhc2UgaXMgdGhlIGBhY3Rpb25CdXR0b24oKWAuIFRoZSBzZWNvbmQgcGFyYW1ldGVyIGlzIHRoZSBmdW5jdGlvbiB0aGF0IHJ1bnMgd2hlbiB0aGUgYGV2ZW50UmVhY3RpdmUoKWAgaXMgdHJpZ2dlcmVkLiAgCmBgYHtyLCBldmFsPUZBTFNFfQpsaWJyYXJ5KHNoaW55KQp1aSA8LSBmbHVpZFBhZ2UoCiAgdGV4dElucHV0KGlucHV0SWQgPSAibmFtZSIsCiAgICAgICAgICAgIGxhYmVsID0gIldoYXQgaXMgeW91ciBuYW1lPyIpLAogIGFjdGlvbkJ1dHRvbihpbnB1dElkID0gInRyaWdnZXIiLCBsYWJlbCA9ICJUcmlnZ2VyIiksCiAgdGV4dE91dHB1dChvdXRwdXRJZCA9ICJ0ZXh0IikKKQpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCwgc2Vzc2lvbikgewogIGdyZWV0aW5nIDwtIGV2ZW50UmVhY3RpdmUoaW5wdXQkdHJpZ2dlciwgewogICAgcGFzdGUwKCJIZWxsbyAiLCBpbnB1dCRuYW1lLCAiISIpCiAgfSkKICBvdXRwdXQkdGV4dCA8LSByZW5kZXJUZXh0KHsKICAgIGdyZWV0aW5nKCkKICB9KQp9CnNoaW55QXBwKHVpLCBzZXJ2ZXIpCmBgYAoKVGhlcmUgaXMgYW5vdGhlciBmdW5jdGlvbiBjYWxsZWQgYG9ic2VydmVFdmVudCgpYCB0aGF0IEkgbGlrZSB0byB1c2UgZm9yIG15IFNoaW55IGFwcHMsIHRoYXQgaXMgdmVyeSBzaW1pbGFyIHRvIGBldmVudFJlYWN0aXZlKClgLiBUaGlzIGlzIHVzZWZ1bCBpZiB5b3Ugd2FudCB0byB0cmlnZ2VyIHNvbWUgY29kZSB3aXRob3V0IHJldHVybmluZyBpdHMgb3V0cHV0IGludG8gYSBuZXcgb2JqZWN0LiBZb3UgY2FuIGxlYXJuIG1vcmUgYWJvdXQgdGhlbSBoZXJlOiBodHRwczovL21hc3RlcmluZy1zaGlueS5vcmcvYmFzaWMtcmVhY3Rpdml0eS5odG1sI29ic2VydmVycyAKCiMjIyMgQ2hhbGxlbmdlIQpOb3csIGxldCdzIHJldmlzaXQgdGhlIGFwcGxpY2F0aW9uIHRoYXQgd2UgaGFkIGF0IHRoZSBlbmQgb2YgdGhlIFVJIHNlY3Rpb24uIFdpdGggd2hhdCB5b3UgbGVhcm5lZCBhYm91dCAqKnJlYWN0aXZpdHkqKiwgY2FuIHlvdSBjaGFuZ2UgdGhlIGJlaGF2aW91ciBvZiB0aGlzIGFwcGxpY2F0aW9uIHNvIHRoYXQgd2hlbiB0aGUgYnV0dG9uIGlzIHByZXNzZWQsIHRoZSBVSSBvdXRwdXRzIHVwZGF0ZSB0byBwcm92aWRlIGEgZ3JlZXRpbmcgdG8gdGhlIGlucHV0IG5hbWUgYW5kIG11bHRpcGxpZXMgdGhlIHR3byBpbnB1dCBudW1iZXJzIHRvZ2V0aGVyPyAKYGBge3IsIGV2YWw9RkFMU0V9CmxpYnJhcnkoc2hpbnkpCnVpIDwtIGZsdWlkUGFnZSgKICB0ZXh0SW5wdXQoaW5wdXRJZCA9ICJuYW1lIiwKICAgICAgICAgICAgbGFiZWwgPSAiV2hhdCBpcyB5b3VyIG5hbWU/IiksCiAgbnVtZXJpY0lucHV0KGlucHV0SWQgPSAibnVtYmVyIiwKICAgICAgICAgICAgICAgbGFiZWwgPSAiRW50ZXIgYSBudW1iZXIiLAogICAgICAgICAgICAgICB2YWx1ZSA9IDEyLAogICAgICAgICAgICAgICBtaW4gPSAwLAogICAgICAgICAgICAgICBtYXggPSAyMDApLAogIG51bWVyaWNJbnB1dChpbnB1dElkID0gIm51bWJlcjIiLAogICAgICAgICAgICAgICBsYWJlbCA9ICJFbnRlciBhIG51bWJlciIsCiAgICAgICAgICAgICAgIHZhbHVlID0gMTIsCiAgICAgICAgICAgICAgIG1pbiA9IDAsCiAgICAgICAgICAgICAgIG1heCA9IDIwMCksCiAgYWN0aW9uQnV0dG9uKGlucHV0SWQgPSAiY2xpY2siLAogICAgICAgICAgICAgICBsYWJlbCA9ICJDbGljayBNZSEiKSwKICB0ZXh0T3V0cHV0KG91dHB1dElkID0gInRleHQiKSwKICB2ZXJiYXRpbVRleHRPdXRwdXQob3V0cHV0SWQgPSAiY29kZSIpCikKc2VydmVyIDwtIGZ1bmN0aW9uKGlucHV0LCBvdXRwdXQsIHNlc3Npb24pIHsKICBvdXRwdXQkdGV4dCA8LSByZW5kZXJUZXh0KHsKICAgICJIZWxsbyBmcmllbmQhIgogIH0pCiAgb3V0cHV0JGNvZGUgPC0gcmVuZGVyUHJpbnQoewogICAgNSAqIDIwCiAgfSkKfQpzaGlueUFwcCh1aSwgc2VydmVyKQpgYGAKCiMjIyBMb2FkaW5nIERhdGEgaW50byBTaGlueSBhcHBzCk9uZSBsYXN0IHRoaW5nIHdlIG5lZWQgdG8gbGVhcm4gaXMgaG93IHRvIGxvYWQgaW4gZGF0YSBpbnRvIHRoZSBTaGlueSBhcHAuIFdlIHdpbGwgZGVtb25zdHJhdGUgdGhpcyB3aXRoIGEgdmVyeSBzaW1wbGUgU2hpbnkgYXBwIHRoYXQganVzdCBwcmludHMgdGhlIGNvZGUgb3V0cHV0LgpgYGB7ciwgZXZhbD1GQUxTRX0KbGlicmFyeShzaGlueSkKCiMgVG9wIExldmVsOgojIENvZGUgYXQgdGhpcyBsZXZlbCBydW5zIG9uY2Ugd2hlbiB0aGUgYXBwbGljYXRpb24gaXMgbGF1bmNoZWQgCgp1aSA8LSBmbHVpZFBhZ2UoCiAgdmVyYmF0aW1UZXh0T3V0cHV0KG91dHB1dElkID0gImNvZGUiKQopCnNlcnZlciA8LSBmdW5jdGlvbihpbnB1dCwgb3V0cHV0LCBzZXNzaW9uKSB7CiAgIyBNaWRkbGUgTGV2ZWw6IAogICMgQ29kZSBhdCB0aGlzIGxldmVsIHJ1bnMgb25jZSB3aGVuIGEgdXNlciB2aXNpdHMgdGhlIGFwcAogIG91dHB1dCRjb2RlIDwtIHJlbmRlclByaW50KHsKICAgICMgQm90dG9tIExldmVsOiAKICAgICMgQ29kZSBhdCB0aGlzIGxldmVsIHJ1bnMgZXZlcnkgdGltZSBpdHMgY29udGVudHMgdXBkYXRlCiAgICBwcmludCgiVGVzdGluZyEiKQogIH0pCn0Kc2hpbnlBcHAodWksIHNlcnZlcikKYGBgCgpTaW5jZSB3ZSBvbmx5IG5lZWQgdG8gbG9hZCBpbiBvdXIgZGF0YSBvbmNlIHdoZW4gdGhlIGFwcGxpY2F0aW9uIGxhdW5jaGVzLCB3ZSBjYW4gcHV0IHRoZSBsaW5lIG9mIGNvZGUgYXQgdGhlIHRvcCBsZXZlbC4gV2UgY2FuIGNoYW5nZSB0aGUgYHByaW50KClgIGluIGBvdXRwdXQkY29kZWAgdG8gc2hvdyB0aGF0IHRoZSBkYXRhIGhhcyBiZWVuIGxvYWRlZC4gIApgYGB7ciwgZXZhbD1GQUxTRX0KbGlicmFyeShzaGlueSkKbGlicmFyeShzZikKCiMgVG9wIExldmVsOgojIENvZGUgYXQgdGhpcyBsZXZlbCBydW5zIG9uY2Ugd2hlbiB0aGUgYXBwbGljYXRpb24gaXMgbGF1bmNoZWQgCmJsYWNrX2JlYXJfb2JzIDwtIHN0X3JlYWQoImRhdGEvYmxhY2tfYmVhcl9vYnNlcnZhdGlvbnNfYmMvYmxhY2tfYmVhcl9vYnNlcnZhdGlvbnNfYmMuc2hwIikKCnVpIDwtIGZsdWlkUGFnZSgKICB2ZXJiYXRpbVRleHRPdXRwdXQob3V0cHV0SWQgPSAiY29kZSIpCikKc2VydmVyIDwtIGZ1bmN0aW9uKGlucHV0LCBvdXRwdXQsIHNlc3Npb24pIHsKICAjIE1pZGRsZSBMZXZlbDogCiAgIyBDb2RlIGF0IHRoaXMgbGV2ZWwgcnVucyBvbmNlIHdoZW4gYSB1c2VyIHZpc2l0cyB0aGUgYXBwCiAgb3V0cHV0JGNvZGUgPC0gcmVuZGVyUHJpbnQoewogICAgIyBCb3R0b20gTGV2ZWw6IAogICAgIyBDb2RlIGF0IHRoaXMgbGV2ZWwgcnVucyBldmVyeSB0aW1lIGl0cyBjb250ZW50cyB1cGRhdGUKICAgIHByaW50KGJsYWNrX2JlYXJfb2JzKQogIH0pCn0Kc2hpbnlBcHAodWksIHNlcnZlcikKYGBgCgpIZXJlIGlzIGEgc291cmNlIHRoYXQgZXhwbGFpbnMgaG93IHRoaXMgd29ya3M6IGh0dHBzOi8vc2hpbnkucG9zaXQuY28vci9nZXRzdGFydGVkL3NoaW55LWJhc2ljcy9sZXNzb241LwoKIyMgUGFydCA0OiBTcGF0aWFsIERhdGEgYW5kIFNoaW55IEFwcHMgCkZ1cnRoZXIgcmVhZGluZzogaHR0cHM6Ly9yc3R1ZGlvLmdpdGh1Yi5pby9sZWFmbGV0L2FydGljbGVzL3NoaW55Lmh0bWwgCgpCZWxpZXZlIGl0IG9yIG5vdCwgd2UgYWxyZWFkeSBoYXZlIHRoZSBmb3VuZGF0aW9ucyBuZWNlc3NhcnkgdG8gY3JlYXRlIGEgc2ltcGxlIHdlYiBtYXAgd2l0aCBTaGlueSEgTGV0J3MgYnVpbGQgb2ZmIHRoZSBzaW1wbGUgYXBwIHdlIGNyZWF0ZWQgZm9yIGxvYWRpbmcgZGF0YSBieSBhZGRpbmcgLyBzd2FwcGluZyBhIGNvdXBsZSBsaW5lcyBvZiBjb2RlLiBUaGlzIGNvbnRpbnVlcyB0byBmb2xsb3cgdGhlIHBhdHRlcm4gb2YgdGhlIGBzb21ldGhpbmdPdXRwdXQoKWAgb24gdGhlIFVJIHNpZGUsIHBhaXJlZCB3aXRoIHRoZSBgcmVuZGVyU29tZXRoaW5nKClgIGFzc2lnbmVkIHRvIHRoZSBgb3V0cHV0JGlkYC4gCgpBIG5ldyBmdW5jdGlvbiB3ZSBhcmUgaW50cm9kdWNpbmcgaXMgdGhlIGBsZWFmbGV0KClgIGZ1bmN0aW9uLiBXZSBkb24ndCBuZWVkIHRvIHVuZGVyc3RhbmQgaG93IHRvIHVzZSBpdCBpbiBkZXB0aCBhdCB0aGlzIHBvaW50LCBidXQgaXQgaXMgc29ydCBvZiBzaW1pbGFyIHRvIHVzaW5nIHNvbWV0aGluZyBsaWtlIGBnZ3Bsb3QoKWAgYnV0IHNwZWNpZmljYWxseSBmb3Igd2ViIG1hcHMuIApgYGB7ciwgZXZhbD1GQUxTRX0KbGlicmFyeShzaGlueSkKbGlicmFyeShzZikKbGlicmFyeShsZWFmbGV0KQoKYmxhY2tfYmVhcl9vYnMgPC0gc3RfcmVhZCgiZGF0YS9ibGFja19iZWFyX29ic2VydmF0aW9uc19iYy9ibGFja19iZWFyX29ic2VydmF0aW9uc19iYy5zaHAiKQoKdWkgPC0gZmx1aWRQYWdlKAogICMgV2UgY2FuIHVzZSBsZWFmbGV0T3V0cHV0IGxpa2UgdGhlIG90aGVyIG91dHB1dHMKICAjIEp1c3QgbmVlZCB0byBwYXNzIGEgdmFsdWUgdG8gb3V0cHV0SWQKICBsZWFmbGV0T3V0cHV0KG91dHB1dElkID0gIm15bWFwIikKKQpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCwgc2Vzc2lvbikgewogICMgQ3JlYXRpbmcgdGhlIG91dHB1dCB0byBteW1hcCwgd2hpY2ggaXMgb3VyIGxlYWZsZXRPdXRwdXQKICBvdXRwdXQkbXltYXAgPC0gcmVuZGVyTGVhZmxldCh7CiAgICAjIFdlIGNyZWF0ZSB0aGUgbGVhZmxldCBvYmplY3QgdGhhdCB3aWxsIGJlIGRpc3BsYXllZCBpbiB0aGUgYXBwCiAgICBsZWFmbGV0KCkKICB9KQp9CnNoaW55QXBwKHVpLCBzZXJ2ZXIpCmBgYAoKIVtUaGUgbW9zdCBiYXNpYyBTaGlueSBMZWFmbGV0IHdlYiBhcHBdKGltYWdlcy9iYXNpY19sZWFmbGV0XzEucG5nKQojIyMgQmFzZSBNYXBzIApUaGlzIGlzIGNvbXBsZXRlbHkgZW1wdHkgbWFwIHRob3VnaCwgd2hpY2ggaXNuJ3QgdmVyeSB1c2VmdWwuIExldCdzIGFkZCBhICoqYmFzZSBtYXAqKi4gQSAqKmJhc2UgbWFwKiogaXMgZXNzZW50aWFsbHkgYW4gaW1hZ2UgdGhhdCBpcyBzcGF0aWFsbHkgcmVmZXJlbmNlZCwgYW5kIHVzdWFsbHkgdXNlZCBqdXN0IGZvciBwcm92aWRpbmcgdmlzdWFsIGNvbnRleHQgdG8gdGhlIHNwYXRpYWwgZGF0YSBvbiB0aGUgbWFwLiBXZSBnZW5lcmFsbHkgY2Fubm90IHVzZSB0aGUgKipiYXNlIG1hcCoqIGZvciBhbnkgYWRkaXRpb25hbCBzcGF0aWFsIGFuYWx5c2VzIG9yIGFjY2VzcyB0aGUgaW5wdXQgZGF0YSB0aGF0IHdhcyB1c2VkIHRvIGNyZWF0ZSB0aGVtIHRocm91Z2ggdGhlICoqYmFzZSBtYXAqKiBpdHNlbGYuIApgYGB7ciwgZXZhbD1GQUxTRX0KbGlicmFyeShzaGlueSkKbGlicmFyeShzZikKbGlicmFyeShsZWFmbGV0KQoKYmxhY2tfYmVhcl9vYnMgPC0gc3RfcmVhZCgiZGF0YS9ibGFja19iZWFyX29ic2VydmF0aW9uc19iYy9ibGFja19iZWFyX29ic2VydmF0aW9uc19iYy5zaHAiKQoKdWkgPC0gZmx1aWRQYWdlKAogIGxlYWZsZXRPdXRwdXQob3V0cHV0SWQgPSAibXltYXAiKQopCnNlcnZlciA8LSBmdW5jdGlvbihpbnB1dCwgb3V0cHV0LCBzZXNzaW9uKSB7CiAgb3V0cHV0JG15bWFwIDwtIHJlbmRlckxlYWZsZXQoewogICAgbGVhZmxldCgpICU+JQogICAgICAjIFRoaXMgaXMgaG93IHdlIGFkZCBhIGJhc2VtYXAgb24gdGhlIGxlYWZsZXQKICAgICAgIyBwcm92aWRlciBiYXNpY2FsbHkgbWVhbnMgd2hpY2ggYmFzZSBtYXAgdG8gdXNlIAogICAgICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVyID0gcHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pCiAgfSkKfQpzaGlueUFwcCh1aSwgc2VydmVyKQoKYGBgCgohW0Jhc2ljIExlYWZsZXQgYXBwIHdpdGggYmFzZSBtYXBdKGltYWdlcy9sZWFmbGV0X2Jhc2VtYXAucG5nKQoKVGhpcyBpcyBzdGFydGluZyB0byBsb29rIG1vcmUgbGlrZSB3aGF0IHdlIHRoaW5rIG9mIGFzIGEgd2ViIG1hcCEgV2UgY2FuIHpvb20gaW50byB0aGUgbWFwLCBjaGFuZ2Ugb3VyIHZpZXcgb2YgdGhlIG1hcCwgZXRjLiBBdCB0aGlzIHBvaW50IHRob3VnaCwgdGhpcyBkb2Vzbid0IGhhdmUgYW55IHJlYWwgZGF0YSB0aGF0IHdlIHdvdWxkIGxpa2UgdG8gc2hvdy4gTGV0J3MgY2hhbmdlIHRoYXQuIAoKIyMjIEFkZGluZyBEYXRhIHRvIExlYWZsZXQgTWFwCkxldCdzIGFkZCBvdXIgYmxhY2sgYmVhciBvYnNlcnZhdGlvbnMgb250byB0aGlzIG1hcC4gVGhpcyBpcyBhY3R1YWxseSByZWFsbHkgZWFzeSBhcyB3ZWxsLCB3ZSBqdXN0IG5lZWQgdG8gYWRkIGEgc2luZ2xlIGxpbmUgb2YgY29kZSBhbmQgdXNlIHRoZSBgYWRkQ2lyY2xlcygpYCBmdW5jdGlvbiwgZmVlZGluZyB0aGUgYGRhdGFgIHBhcmFtZXRlciB3aXRoIHRoZSBwb2ludCBkYXRhc2V0IChgYmxhY2tfYmVhcl9vYnNgKQpgYGB7ciwgZXZhbD1GQUxTRX0KbGlicmFyeShzaGlueSkKbGlicmFyeShzZikKbGlicmFyeShsZWFmbGV0KQoKYmxhY2tfYmVhcl9vYnMgPC0gc3RfcmVhZCgiZGF0YS9ibGFja19iZWFyX29ic2VydmF0aW9uc19iYy9ibGFja19iZWFyX29ic2VydmF0aW9uc19iYy5zaHAiKQoKdWkgPC0gZmx1aWRQYWdlKAogIGxlYWZsZXRPdXRwdXQob3V0cHV0SWQgPSAibXltYXAiKQopCnNlcnZlciA8LSBmdW5jdGlvbihpbnB1dCwgb3V0cHV0LCBzZXNzaW9uKSB7CiAgb3V0cHV0JG15bWFwIDwtIHJlbmRlckxlYWZsZXQoewogICAgbGVhZmxldCgpICU+JQogICAgICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVyID0gcHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pICU+JQogICAgICAjIFRoaXMgaXMgaG93IHdlIGFkZCBwb2ludHMgdG8gYSBsZWFmbGV0IG1hcAogICAgICBhZGRDaXJjbGVzKGRhdGEgPSBibGFja19iZWFyX29icykKICB9KQp9CnNoaW55QXBwKHVpLCBzZXJ2ZXIpCmBgYAohW0Jhc2ljIGNpcmNsZSBzeW1ib2xzIGZvciBwb2ludCBkYXRhXShpbWFnZXMvbGVhZmxldF9hZGRDaXJjbGVzLnBuZykKCldlIGNhbiBhbHNvIGFkZCBwb3B1cHMgdG8gZWFjaCBvZiB0aGVzZSBjaXJjbGVzIGFzIHdlbGwsIHNvIHRoYXQgaXQgc2hvd3Mgc29tZSBpbmZvcm1hdGlvbiBmcm9tIHRoZSBzcGF0aWFsIGRhdGEgd2hlbiB0aGUgY2lyY2xlIGlzIGNsaWNrZWQgb24uIFRoaXMgaXMgcmVsYXRpdmVseSBzaW1wbGUgYXMgd2VsbC4gV2UganVzdCBhZGQgdGhlIGBwb3B1cGAgcGFyYW1ldGVyIGluIGBhZGRDaXJjbGVzKClgIHdpdGggdGhlIGB+Y29sdW1uYCB0aGF0IHdlIHdhbnQgdG8gaGF2ZSBhcHBlYXIgaW4gdGhlIHBvcHVwLiAKYGBge3IsIGV2YWw9RkFMU0V9CmFkZENpcmNsZXMoZGF0YSA9IGJsYWNrX2JlYXJfb2JzLAogICAgICAgICAgIHBvcHVwID0gfm9ic2VydmVkXzEpCmBgYAohW0Jhc2ljIHBvcHVwc10oaW1hZ2VzL2xlYWZsZXRfYWRkQ2lyY2xlc19wb3B1cC5wbmcpCgpUaGlzIGlzIGVzc2VudGlhbGx5IHRoZSBzYW1lIGZvciB0aGUgb3RoZXIgc3BhdGlhbCBkYXRhIGFzIHdlbGwsIGp1c3Qgd2l0aCBkaWZmZXJlbnQgZnVuY3Rpb25zLiAKYGBge3IsIGV2YWw9RkFMU0V9CiMgRm9yIGFkZGluZyBwb2x5Z29ucwpsZWFmbGV0KCkgJT4lCiAgYWRkUG9seWdvbnMoZGF0YSA9IHBvbHlnb25zKQoKIyBGb3IgYWRkaW5nIGxpbmVzCmxlYWZsZXQoKSAlPiUKICBhZGRQb2x5bGluZXMoZGF0YSA9IHBvbHlsaW5lcykKCiMgRm9yIGFkZGluZyByYXN0ZXJzCmxlYWZsZXQoKSAlPiUKICBhZGRSYXN0ZXJJbWFnZShkYXRhID0gcmFzdGVyX2RhdGEpCmBgYAoKIyMjIyBDaGFsbGVuZ2UhIApDYW4geW91IGFkZCB0aGUgQnVybmFieSBCb3VuZGFyeSBwb2x5Z29uIHRvIHRoZSBtYXA/IAoKYGBge3IsIGV2YWw9RkFMU0V9CmxpYnJhcnkoc2hpbnkpCmxpYnJhcnkoc2YpCmxpYnJhcnkobGVhZmxldCkKCmJsYWNrX2JlYXJfb2JzIDwtIHN0X3JlYWQoImRhdGEvYmxhY2tfYmVhcl9vYnNlcnZhdGlvbnNfYmMvYmxhY2tfYmVhcl9vYnNlcnZhdGlvbnNfYmMuc2hwIikKYnVybmFieSA8LSBzdF9yZWFkKCJkYXRhL0J1cm5hYnlfQm91bmRhcnkvQnVybmFieV9Cb3VuZGFyeS5zaHAiKQoKdWkgPC0gZmx1aWRQYWdlKAogIGxlYWZsZXRPdXRwdXQob3V0cHV0SWQgPSAibXltYXAiKQopCnNlcnZlciA8LSBmdW5jdGlvbihpbnB1dCwgb3V0cHV0LCBzZXNzaW9uKSB7CiAgb3V0cHV0JG15bWFwIDwtIHJlbmRlckxlYWZsZXQoewogICAgbGVhZmxldCgpICU+JQogICAgICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVyID0gcHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pICU+JQogICAgICBhZGRDaXJjbGVzKGRhdGEgPSBibGFja19iZWFyX29icywKICAgICAgICAgICAgICAgICBwb3B1cCA9IH5vYnNlcnZlZF8xKQogIH0pCn0Kc2hpbnlBcHAodWksIHNlcnZlcikKYGBgCgojIyMgUHV0dGluZyBldmVyeXRoaW5nIHRvZ2V0aGVyISAKV2UgYXJlIGdvaW5nIHRvIG1ha2UgYSB3ZWIgbWFwIG5vdywgd2l0aCB0aGUgYWJpbGl0eSBmb3IgdGhlIHVzZXIgdG8gZmlsdGVyIHBvaW50cyBiYXNlZCBvZmYgb2YgZGF0ZXMgb2Ygb2JzZXJ2YXRpb25zLiBUaGlzIHdpbGwgcmVhbGx5IGJyaW5nIHRvZ2V0aGVyIHdoYXQgd2UgaGF2ZSBsZWFybnQgc28gZmFyLiBXZSB3aWxsIGFsc28gaW50cm9kdWNlIGFub3RoZXIgbGlicmFyeSBgYnNsaWJgIGZvciBlbmhhbmNpbmcgdGhlIGxvb2sgYW5kIGZlZWwgb2Ygb3VyIHdlYiBtYXAuIAoKVG8gc3RhcnQsIHdlJ2xsIGludHJvZHVjZSB0aGlzIHRlbXBsYXRlIGZvciB0aGUgYXBwOiAKYGBge3IsIGV2YWw9RkFMU0V9CmxpYnJhcnkoc2hpbnkpCmxpYnJhcnkoYnNsaWIpCmxpYnJhcnkoc2YpCmxpYnJhcnkobGVhZmxldCkKCiMgRGVmaW5lIFVJIC0tLS0KIyBUaGlzIGlzIHRoZSBtYWluIGRpZmZlcmVuY2UgZnJvbSB3aGF0IHdlIGhhdmUgYmVlbiBkb2luZwojIFdlIHVzZSBwYWdlX3NpZGViYXIoKSBpbnN0ZWFkIG9mIGZsdWlkUGFnZSgpIGZvciB0aGUgVUkKdWkgPC0gcGFnZV9zaWRlYmFyKAogCikKCiMgRGVmaW5lIHNlcnZlciBsb2dpYyAtLS0tCnNlcnZlciA8LSBmdW5jdGlvbihpbnB1dCwgb3V0cHV0KSB7Cgp9CgojIFJ1biB0aGUgYXBwIC0tLS0Kc2hpbnlBcHAodWkgPSB1aSwgc2VydmVyID0gc2VydmVyKQpgYGAKCkxldCdzIGFkZCBvdXIgdGl0bGUgYW5kIHNpZGViYXI6CmBgYHtyLCBldmFsPUZBTFNFfQpsaWJyYXJ5KHNoaW55KQpsaWJyYXJ5KGJzbGliKQpsaWJyYXJ5KHNmKQpsaWJyYXJ5KGxlYWZsZXQpCgojIERlZmluZSBVSSAtLS0tCiMgVGhpcyBpcyB0aGUgbWFpbiBkaWZmZXJlbmNlIGZyb20gd2hhdCB3ZSBoYXZlIGJlZW4gZG9pbmcKIyBXZSB1c2UgcGFnZV9zaWRlYmFyKCkgaW5zdGVhZCBvZiBmbHVpZFBhZ2UoKSBmb3IgdGhlIFVJCnVpIDwtIHBhZ2Vfc2lkZWJhcigKICB0aXRsZSA9ICJCbGFjayBCZWFyIE9ic2VydmF0aW9ucyBXZWIgTWFwIiwKICBzaWRlYmFyID0gc2lkZWJhcigiRmlsdGVyIGJ5IERhdGUiKSwKICAiVGhpcyBpcyB3aGVyZSBvdXIgbWFpbiBjb250ZW50IHdpbGwgZ28iCikKCiMgRGVmaW5lIHNlcnZlciBsb2dpYyAtLS0tCnNlcnZlciA8LSBmdW5jdGlvbihpbnB1dCwgb3V0cHV0KSB7Cgp9CgojIFJ1biB0aGUgYXBwIC0tLS0Kc2hpbnlBcHAodWkgPSB1aSwgc2VydmVyID0gc2VydmVyKQpgYGAKIVtCYXNpYyBzaWRlYmFyIGxheW91dF0oaW1hZ2VzL2Jhc2ljX3NpZGViYXIucG5nKQoKTGV0J3MgYWRkIHNvbWUgbW9yZSBVSSB0byBvdXIgYXBwLCBpbmNsdWRpbmcgYSBkYXRlIHJhbmdlIGlucHV0IGluIHRoZSBzaWRlYmFyIGFuZCBhIGJhc2ljIExlYWZsZXQgbWFwIGFzIHRoZSBtYWluIGNvbnRlbnQ6IApgYGB7ciwgZXZhbD1GQUxTRX0KbGlicmFyeShzaGlueSkKbGlicmFyeShic2xpYikKbGlicmFyeShzZikKbGlicmFyeShsZWFmbGV0KQoKIyBEZWZpbmUgVUkgLS0tLQp1aSA8LSBwYWdlX3NpZGViYXIoCiAgdGl0bGUgPSAiQmxhY2sgQmVhciBPYnNlcnZhdGlvbnMgV2ViIE1hcCIsCiAgc2lkZWJhciA9IHNpZGViYXIoIkZpbHRlciBieSBEYXRlIiwKICAgICAgICAgICAgICAgICAgICBkYXRlUmFuZ2VJbnB1dChpbnB1dElkID0gImRhdGVfcmFuZ2UiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gIkRhdGUgUmFuZ2UiKSwKICAgICAgICAgICAgICAgICAgICBhY3Rpb25CdXR0b24oaW5wdXRJZCA9ICJmaWx0ZXJfYWN0aW9uIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSAiRmlsdGVyIikKICApLAogIGxlYWZsZXRPdXRwdXQob3V0cHV0SWQgPSAibXltYXAiKQopCgojIERlZmluZSBzZXJ2ZXIgbG9naWMgLS0tLQpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCkgewogIG91dHB1dCRteW1hcCA8LSByZW5kZXJMZWFmbGV0KHsKICAgIGxlYWZsZXQoKSAlPiUKICAgICAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlciA9IHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uKQogIH0pCn0KCiMgUnVuIHRoZSBhcHAgLS0tLQpzaGlueUFwcCh1aSA9IHVpLCBzZXJ2ZXIgPSBzZXJ2ZXIpCmBgYAohW0NyZWF0aW5nIFVJIHdpdGggYnNsaWJdKGltYWdlcy9ic2xpYl9VSS5wbmcpCgpOb3csIGxldCdzIGFkZCByZWFkaW5nIGluIHRoZSBzcGF0aWFsIGRhdGEgYW5kIGJhc2ljIHZpc3VhbGl6YXRpb24gb2YgaXQgdG8gdGhlIGFwcDoKYGBge3IsIGV2YWw9RkFMU0V9CmxpYnJhcnkoc2hpbnkpCmxpYnJhcnkoYnNsaWIpCmxpYnJhcnkoc2YpCmxpYnJhcnkobGVhZmxldCkKCiMgUmVhZGluZyBpbiB0aGUgYmxhY2sgYmVhciBvYnNlcnZhdGlvbnMKYmxhY2tfYmVhcl9vYnMgPC0gc3RfcmVhZCgiZGF0YS9ibGFja19iZWFyX29ic2VydmF0aW9uc19iYy9ibGFja19iZWFyX29ic2VydmF0aW9uc19iYy5zaHAiKQoKIyBEZWZpbmUgVUkgLS0tLQp1aSA8LSBwYWdlX3NpZGViYXIoCiAgdGl0bGUgPSAiQmxhY2sgQmVhciBPYnNlcnZhdGlvbnMgV2ViIE1hcCIsCiAgc2lkZWJhciA9IHNpZGViYXIoIkZpbHRlciBieSBEYXRlIiwKICAgICAgICAgICAgICAgICAgICBkYXRlUmFuZ2VJbnB1dChpbnB1dElkID0gImRhdGVfcmFuZ2UiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gIkRhdGUgUmFuZ2UiKSwKICAgICAgICAgICAgICAgICAgICBhY3Rpb25CdXR0b24oaW5wdXRJZCA9ICJmaWx0ZXJfYWN0aW9uIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSAiRmlsdGVyIikKICApLAogIGxlYWZsZXRPdXRwdXQob3V0cHV0SWQgPSAibXltYXAiKQopCgojIERlZmluZSBzZXJ2ZXIgbG9naWMgLS0tLQpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCkgewogIG91dHB1dCRteW1hcCA8LSByZW5kZXJMZWFmbGV0KHsKICAgIGxlYWZsZXQoKSAlPiUKICAgICAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlciA9IHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uKSAlPiUKICAgICAgIyBBZGRpbmcgdGhlIGNpcmNsZSB2aXN1YWxpemF0aW9ucwogICAgICBhZGRDaXJjbGVzKGRhdGEgPSBibGFja19iZWFyX29icywKICAgICAgICAgICAgICAgICBwb3B1cCA9IH5vYnNlcnZlZF8xKQogIH0pCn0KCiMgUnVuIHRoZSBhcHAgLS0tLQpzaGlueUFwcCh1aSA9IHVpLCBzZXJ2ZXIgPSBzZXJ2ZXIpCmBgYAohW0FkZGluZyB0aGUgYmVhciBwb2ludHMgdG8gdGhlIHdlYiBhcHBsaWNhdGlvbl0oaW1hZ2VzL2JzbGliX3BvaW50c19hZGRlZC5wbmcpCgpMZXQncyBzZXQgdXAgYWNjZXNzaW5nIHRoZSBkYXRlIHJhbmdlIGlucHV0cywgYW5kIHNob3cgdGhlIHNlbGVjdGVkIGRhdGVzIHdpdGggYSBzaW1wbGUgdGV4dCBvdXRwdXQgaW4gdGhlIHNpZGUgYmFyLiAKYGBge3IsIGV2YWw9RkFMU0V9CmxpYnJhcnkoc2hpbnkpCmxpYnJhcnkoYnNsaWIpCmxpYnJhcnkoc2YpCmxpYnJhcnkobGVhZmxldCkKCmJsYWNrX2JlYXJfb2JzIDwtIHN0X3JlYWQoImRhdGEvYmxhY2tfYmVhcl9vYnNlcnZhdGlvbnNfYmMvYmxhY2tfYmVhcl9vYnNlcnZhdGlvbnNfYmMuc2hwIikKCiMgRGVmaW5lIFVJIC0tLS0KdWkgPC0gcGFnZV9zaWRlYmFyKAogIHRpdGxlID0gIkJsYWNrIEJlYXIgT2JzZXJ2YXRpb25zIFdlYiBNYXAiLAogIHNpZGViYXIgPSBzaWRlYmFyKCJGaWx0ZXIgYnkgRGF0ZSIsCiAgICAgICAgICAgICAgICAgICAgZGF0ZVJhbmdlSW5wdXQoaW5wdXRJZCA9ICJkYXRlX3JhbmdlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9ICJEYXRlIFJhbmdlIiksCiAgICAgICAgICAgICAgICAgICAgYWN0aW9uQnV0dG9uKGlucHV0SWQgPSAiZmlsdGVyX2FjdGlvbiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gIkZpbHRlciIpLAogICAgICAgICAgICAgICAgICAgICMgQWRkaW5nIHNvbWUgc2ltcGxlIHRleHQgb3V0cHV0cwogICAgICAgICAgICAgICAgICAgIHRleHRPdXRwdXQob3V0cHV0SWQgPSAidGV4dF9zdGFydF9kYXRlIiksCiAgICAgICAgICAgICAgICAgICAgdGV4dE91dHB1dChvdXRwdXRJZCA9ICJ0ZXh0X2VuZF9kYXRlIikKICApLAogIGxlYWZsZXRPdXRwdXQob3V0cHV0SWQgPSAibXltYXAiKQopCgojIERlZmluZSBzZXJ2ZXIgbG9naWMgLS0tLQpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCkgewogICMgVGhlIGV2ZW50UmVhY3RpdmUgbWFrZXMgdGhpcyB1cGRhdGUgb24gdGhlIGJ1dHRvbiBwcmVzcwogIHN0YXJ0X2RhdGUgPC0gZXZlbnRSZWFjdGl2ZShpbnB1dCRmaWx0ZXJfYWN0aW9uLCB7CiAgICAjIGRhdGVSYW5nZUlucHV0KCkgcHJvdmlkZXMgYSB2ZWN0b3Igb2YgbGVuZ3RoIDIKICAgICMgdGhlIGZpcnN0IG9iamVjdCBpbiB0aGlzIHZlY3RvciBpcyB0aGUgZmlyc3QgZGF0ZQogICAgaW5wdXQkZGF0ZV9yYW5nZVsxXQogIH0pCiAgZW5kX2RhdGUgPC0gZXZlbnRSZWFjdGl2ZShpbnB1dCRmaWx0ZXJfYWN0aW9uLCB7CiAgICAjIHRoZSBzZWNvbmQgb2JqZWN0IGluIHRoaXMgdmVjdG9yIGlzIHRoZSBzZWNvbmQgZGF0ZQogICAgaW5wdXQkZGF0ZV9yYW5nZVsyXQogIH0pCiAgIyBUaGVzZSB3aWxsIGF1dG9tYXRpY2FsbHkgdXBkYXRlIHdoZW4gdGhlIHJlYWN0aXZlIHZhbHVlcyB1cGRhdGUKICAjIHN0YXJ0X2RhdGUoKSBhbmQgZW5kX2RhdGUoKSBhcmUgcmVhY3RpdmUgdmFsdWVzCiAgb3V0cHV0JHRleHRfc3RhcnRfZGF0ZSA8LSByZW5kZXJUZXh0KHsKICAgIHBhc3RlMCgiU3RhcnQgZGF0ZTogIiwgc3RhcnRfZGF0ZSgpKQogIH0pCiAgb3V0cHV0JHRleHRfZW5kX2RhdGUgPC0gcmVuZGVyVGV4dCh7CiAgICBwYXN0ZTAoIkVuZCBkYXRlOiAiLCAgZW5kX2RhdGUoKSkKICB9KQoKICBvdXRwdXQkbXltYXAgPC0gcmVuZGVyTGVhZmxldCh7CiAgICBsZWFmbGV0KCkgJT4lCiAgICAgIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXIgPSBwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbikgJT4lCiAgICAgIGFkZENpcmNsZXMoZGF0YSA9IGJsYWNrX2JlYXJfb2JzLAogICAgICAgICAgICAgICAgIHBvcHVwID0gfm9ic2VydmVkXzEpCiAgfSkKfQoKIyBSdW4gdGhlIGFwcCAtLS0tCnNoaW55QXBwKHVpID0gdWksIHNlcnZlciA9IHNlcnZlcikKYGBgCiFbQnV0dG9uIHByZXNzIHVwZGF0ZXMgdGhlIHN0YXJ0IGFuZCBlbmQgZGF0ZXNdKGltYWdlcy9ic2xpYl90ZXh0X2ZpbHRlcl9kYXRlcy5wbmcpCgpUaW1lIHRvIG1ha2UgdGhlIGJpZyBqdW1wLCBhbmQgYWRkIHNvbWUgYmFzaWMgZGF0YSBwcm9jZXNzaW5nIHdpdGhpbiB0aGUgYXBwbGljYXRpb24uIFdlIHdpbGwgYmUgcXVlcnlpbmcgdGhlIGBibGFja19iZWFyX29ic2AgcG9pbnRzIGJ5IHRoZSBkYXRlcyB0aGF0IGZhbGwgYmV0d2VlbiB0aGUgYHN0YXJ0X2RhdGVgIGFuZCBgZW5kX2RhdGVgLiAKYGBge3IsIGV2YWw9RkFMU0V9CmxpYnJhcnkoc2hpbnkpCmxpYnJhcnkoYnNsaWIpCmxpYnJhcnkoc2YpCmxpYnJhcnkobGVhZmxldCkKCmJsYWNrX2JlYXJfb2JzIDwtIHN0X3JlYWQoImRhdGEvYmxhY2tfYmVhcl9vYnNlcnZhdGlvbnNfYmMvYmxhY2tfYmVhcl9vYnNlcnZhdGlvbnNfYmMuc2hwIikKCiMgRGVmaW5lIFVJIC0tLS0KdWkgPC0gcGFnZV9zaWRlYmFyKAogIHRpdGxlID0gIkJsYWNrIEJlYXIgT2JzZXJ2YXRpb25zIFdlYiBNYXAiLAogIHNpZGViYXIgPSBzaWRlYmFyKCJGaWx0ZXIgYnkgRGF0ZSIsCiAgICAgICAgICAgICAgICAgICAgZGF0ZVJhbmdlSW5wdXQoaW5wdXRJZCA9ICJkYXRlX3JhbmdlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9ICJEYXRlIFJhbmdlIiksCiAgICAgICAgICAgICAgICAgICAgYWN0aW9uQnV0dG9uKGlucHV0SWQgPSAiZmlsdGVyX2FjdGlvbiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gIkZpbHRlciIpLAogICAgICAgICAgICAgICAgICAgICMgQWRkaW5nIHNvbWUgc2ltcGxlIHRleHQgb3V0cHV0cwogICAgICAgICAgICAgICAgICAgIHRleHRPdXRwdXQob3V0cHV0SWQgPSAidGV4dF9zdGFydF9kYXRlIiksCiAgICAgICAgICAgICAgICAgICAgdGV4dE91dHB1dChvdXRwdXRJZCA9ICJ0ZXh0X2VuZF9kYXRlIikKICApLAogIGxlYWZsZXRPdXRwdXQob3V0cHV0SWQgPSAibXltYXAiKQopCgojIERlZmluZSBzZXJ2ZXIgbG9naWMgLS0tLQpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCkgewogIHN0YXJ0X2RhdGUgPC0gZXZlbnRSZWFjdGl2ZShpbnB1dCRmaWx0ZXJfYWN0aW9uLCB7CiAgICBpbnB1dCRkYXRlX3JhbmdlWzFdCiAgfSkKICBlbmRfZGF0ZSA8LSBldmVudFJlYWN0aXZlKGlucHV0JGZpbHRlcl9hY3Rpb24sIHsKICAgIGlucHV0JGRhdGVfcmFuZ2VbMl0KICB9KQogIG91dHB1dCR0ZXh0X3N0YXJ0X2RhdGUgPC0gcmVuZGVyVGV4dCh7CiAgICBwYXN0ZTAoIlN0YXJ0IGRhdGU6ICIsIHN0YXJ0X2RhdGUoKSkKICB9KQogIG91dHB1dCR0ZXh0X2VuZF9kYXRlIDwtIHJlbmRlclRleHQoewogICAgcGFzdGUwKCJFbmQgZGF0ZTogIiwgIGVuZF9kYXRlKCkpCiAgfSkKCiAgIyBGaWx0ZXJpbmcgdGhlIHZhbHVlcyBvZiB0aGUgYmxhY2tfYmVhcl9vYnMKICAjIFRoaXMgbmVlZHMgdG8gYmUgaW4gYSByZWFjdGl2ZSgpLCBzaW5jZSB0aGlzIHdpbGwgdHJpZ2dlciB3aGVuIGl0cyBpbnB1dHMgYXJlIGNoYW5nZWQKICAjIFRoZSBpbnB1dHMgYXJlIGNoYW5nZWQgd2l0aCB0aGUgZXZlbnRSZWFjdGl2ZSgpLCBjb250cm9sbGVkIGJ5IHRoZSBidXR0b24KICAjIFRoZXJlZm9yZSwgdGhpcyBzZWxlY3RlZF9ibGFja19iZWFyX29icyB3aWxsIGFsc28gdXBkYXRlIGVhY2ggdGltZSB0aGUgYnV0dG9uIGlzIHByZXNzZWQKICBzZWxlY3RlZF9ibGFja19iZWFyX29icyA8LSByZWFjdGl2ZSh7CiAgICAjIFRoaXMgcXVlcmllcyBkYXRlcyB0aGF0IGFyZSB3aXRoaW4gdGhlIHN0YXJ0IGFuZCBlbmQgZGF0ZXMKICAgIGJsYWNrX2JlYXJfb2JzW2JsYWNrX2JlYXJfb2JzJG9ic2VydmVkXzEgPj0gc3RhcnRfZGF0ZSgpICYgYmxhY2tfYmVhcl9vYnMkb2JzZXJ2ZWRfMSA8PSBlbmRfZGF0ZSgpLF0KICB9KQoKICBvdXRwdXQkbXltYXAgPC0gcmVuZGVyTGVhZmxldCh7CiAgICBsZWFmbGV0KCkgJT4lCiAgICAgIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXIgPSBwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbikgJT4lCiAgICAgICMgUmVtZW1iZXIgdG8gc3dpdGNoIHRoZSBkYXRhIHRvIHRoZSBzZWxlY3RlZF9ibGFja19iZWFyX29icygpCiAgICAgIGFkZENpcmNsZXMoZGF0YSA9IHNlbGVjdGVkX2JsYWNrX2JlYXJfb2JzKCksCiAgICAgICAgICAgICAgICAgcG9wdXAgPSB+b2JzZXJ2ZWRfMSkKICB9KQp9CgojIFJ1biB0aGUgYXBwIC0tLS0Kc2hpbnlBcHAodWkgPSB1aSwgc2VydmVyID0gc2VydmVyKQpgYGAKIVtGaW5hbCB3ZWIgbWFwIG91dHB1dCEhXShpbWFnZXMvYnNsaWJfdGV4dF9maWx0ZXJfZGF0ZXMucG5nKQoKKipDb25ncmF0dWxhdGlvbnMsIHlvdSBoYXZlIGNyZWF0ZWQgeW91ciBmaXJzdCBmdWxseSBmbGVkZ2VkIFIgU2hpbnkgd2ViIG1hcCEgVGhpcyBpcyBhIGxvdCB0byB0YWtlIGluLCBzbyBwbGVhc2UgcGF0IHlvdXJzZWxmIG9uIHRoZSBiYWNrIGZvciBnZXR0aW5nIHRoaXMgZmFyLioqCgojIyMjIENoYWxsZW5nZSEgCkNhbiB5b3UgY3JlYXRlIGFuIGludGVyYWN0aXZlIHdlYiBtYXAgdGhhdCBkaXNwbGF5cyB0aGUgZ3JpZCBjZWxsIG91dHB1dCB3aXRoIG9ic2VydmF0aW9uIGNvdW50cyBmcm9tIFBhcnQgMj8KCiMjIFBhcnQgNTogUHVibGlzaGluZyBZb3VyIFNoaW55IEFwcHMgCk5vdyB0aGF0IHlvdSBoYXZlIGNyZWF0ZWQgeW91ciB3ZWIgbWFwLCB3ZSB3YW50IHRvIHNoYXJlIGl0ISAKCldlIHdpbGwgYmUgdXNpbmcgd3d3LnNoaW55YXBwcy5pbyB0byBzaGFyZSBvdXIgYXBwbGljYXRpb24gdG8gYW55b25lIGluIHRoZSB3b3JsZCB3aXRoIGFuIGludGVybmV0IGNvbm5lY3Rpb24gYW5kIGEgYnJvd3Nlci4gCgpGdWxsIEdldHRpbmcgU3RhcnRlZCBHdWlkZTogaHR0cHM6Ly9kb2NzLnBvc2l0LmNvL3NoaW55YXBwcy5pby9ndWlkZS9nZXR0aW5nX3N0YXJ0ZWQvCgojIyMgU3RlcCAxOiBDcmVhdGUgYW4gYWNjb3VudApHbyB0byB3d3cuc2hpbnlhcHBzLmlvIGFuZCBmb2xsb3cgdGhlIGluc3RydWN0aW9ucyBmb3IgY3JlYXRpbmcgYW4gYWNjb3VudC4gVGhleSBwcm92aWRlIHRoZSBvcHRpb24gZm9yIGpvaW5pbmcgd2l0aCBHaXRIdWIsIHNvIHlvdSBzaG91bGQgYmUgYWJsZSB0byBvbmUtY2xpY2sgY3JlYXRlIGEgbmV3IGFjY291bnQuIAoKIyMjIFN0ZXAgMjogU2V0IHVwIApJbnN0YWxsIHRoZSBgcnNjb25uZWN0YCBwYWNrYWdlLCB3aGljaCB3ZSBuZWVkIHRvIGNvbm5lY3QgdG8gc2hpbnlhcHBzLmlvIApgYGB7ciwgZXZhbD1GQUxTRX0KaW5zdGFsbC5wYWNrYWdlcygicnNjb25uZWN0IikKbGlicmFyeShyc2Nvbm5lY3QpCmBgYAoKIyMjIFN0ZXAgMzogR2V0IHlvdXIgdG9rZW4KQ2xpY2sgeW91ciBhY2NvdW50IG5hbWUgaW4gdGhlIHVwcGVyIHJpZ2h0IHBhcnQgb2YgdGhlIHBhZ2UsIHRoZW4gY2xpY2sgdGhlICoqVG9rZW5zKiogb3B0aW9uLiAKIVtUb2tlbnMgbWVudSBvcHRpb25dKGltYWdlcy9zaGlueWFwcHNpb190b2tlbi5wbmcpCgpUaGlzIHNob3VsZCBvcGVuIHRoZSAqKlRva2VucyoqIHBhZ2UuIENsaWNrIG9uICoqU2hvdyoqIG9uIG9uZSBvZiB0aGUgdG9rZW5zIGluIHRoZSB0YWJsZSwgYW5kIHRoaXMgc2hvdWxkIG9wZW4gYSBwb3AgdXAgd2l0aCBpbnN0cnVjdGlvbnMgb24gaG93IHRvIGF1dGhvcml6ZSBpbiBSc3R1ZGlvIHRvIGVuYWJsZSBkZXBsb3lpbmcgeW91ciBhcHBsaWNhdGlvbi4gRm9sbG93IHRoZXNlIGluc3RydWN0aW9ucwohW0F1dGhvcml6YXRpb24gaW5zdHJ1Y3Rpb25zXShpbWFnZXMvc2hpbnlhcHBzaW9fYXV0aG9yaXphdGlvbl9pbnN0cnVjdGlvbnMucG5nKQoKIyMjIFN0ZXAgNDogUHVibGlzaCB5b3VyIGFwcCEgCkNpY2sgb24gdGhlICoqUHVibGlzaCoqIGJ1dHRvbiBpbiB0aGUgdXBwZXIgcmlnaHQgY29ybmVyIG9mIHlvdXIgU2hpbnkgYXBwIGNvZGUocHJvYmFibHkgY2FsbGVkIGBhcHAuUmApLiAKIVtQdWJsaXNoIGJ1dHRvbl0oaW1hZ2VzL3NoaW55YXBwc2lvX3B1Ymxpc2gucG5nKQoKWW91IHNob3VsZCBzZWUgaW4gdGhlIHBvcHVwIG1lbnUgdGhhdCB5b3VyIGFjY291bnQgaXMgY29ubmVjdGVkLiBHaXZlIHlvdXIgYXBwbGljYXRpb24gYSBuYW1lIGFuZCBjbGljayB0aGF0IHB1Ymxpc2ggYnV0dG9uLiBUaGlzIG1heSB0YWtlIGEgbGl0dGxlIHdoaWxlIHRvIGRlcGxveSwgc28gdGFrZSB0aGlzIG9wcG9ydHVuaXR5IHRvIHN0YW5kIHVwIGFuZCBzdHJldGNoLCBtb3ZlIGFyb3VuZCwgcmVsYXguIAohW1B1Ymxpc2hpbmcgeW91ciBhcHAgdG8gdGhlIHdvcmxkIV0oaW1hZ2VzL3NoaW55YXBwc2lvX3B1Ymxpc2hfZm9yX3JlYWwucG5nKQoKIyMjIFN0ZXAgNTogU2hhcmUgeW91ciBhcHAgd2l0aCB5b3VyIGZyaWVuZHMgYW5kIGNvbGxlYWd1ZXMhIApZb3UgY2FuIGdvIG9udG8gd3d3LnNoaW55YXBwcy5pbyBhbmQgbG9nIGluIHRvIHlvdXIgYWNjb3VudC4gWW91IHNob3VsZCBiZSBhYmxlIHRvIHNlZSB5b3VyIHdlYiBtYXAgaW4gdGhlICoqQXBwbGljYXRpb25zKiogcGFnZS4gQ2xpY2sgb24gaXQsIGFuZCBpdCBzaG91bGQgcHJvdmlkZSB0aGUgZGV0YWlscyBmb3IgeW91ciB3ZWIgbWFwLiBUaGVyZSBzaG91bGQgYmUgYSBsaW5rIHRvIGFjY2VzcyB5b3VyIGFwcCBoZXJlISAKIVtTaGlueSBhcHAgZGV0YWlscyBvbiBzaGlueWFwcHMuaW9dKGltYWdlcy9zaGlueWFwcHNpb19hcHBsaWNhdGlvbl9kZXRhaWxzLnBuZykKCioqSWYgZXZlcnl0aGluZyBoYXMgZ29uZSBjb3JyZWN0bHksIHlvdSBzaG91bGQgYmUgZG9uZSEgQ29uZ3JhdHVsYXRpb25zISoqCgpIZXJlIGlzIG15IGV4YW1wbGUgU2hpbnkgYXBwIGZvciB0aGlzIHdvcmtzaG9wOiBodHRwczovL2pheW1hdHN1c2hpYmEuc2hpbnlhcHBzLmlvL1R1dG9yaWFsLVItU2hpbnktV2ViLU1hcHBpbmcvIAoKIyMgVGhhbmsgeW91IGZvciBmb2xsb3dpbmcgYWxvbmchIElmIHlvdSBuZWVkIGFueSBoZWxwLCBmZWVsIGZyZWUgdG8gcmVhY2ggb3V0IGF0IGpheV9tYXRzdXNoaWJhQHNmdS5jYSAKClRoaXMgc2l0ZSB3YXMgb3JpZ2luYWxseSBkZXZlbG9wZWQgZm9yICoqV2ViIE1hcHBpbmcgd2l0aCBSIFNoaW55IFdvcmtzaG9wIFNlcmllcyoqIGF0IFNGVSBkZWxpdmVyZWQgb24gU2VwdGVtYmVyIDIwIGFuZCBTZXB0ZW1iZXIgMjcsIDIwMjQuCgo=